techinfoBiT
  • News
  • Startups
  • Tech
    • Internet
    • Security
  • WebMaster
    • All
    • GEO
    • SEO
    • Server & Hosting
    • Tools
    • WordPress
    'Sorry, This File Type Is Not Permitted for Security Reasons' How To Fix This Error in WordPress Website Development-techinfoBiT

    ‘Sorry, This File Type Is Not Permitted for Security Reasons’ How To Fix This Error in WordPress

    What is DMARC Record in DNS and Why It is Important For Email Security-techinfoBiT

    What is DMARC Record in DNS and Why It is Important For Email Security?

    How To Generate SHA-256 Hash From the Command Line on Ubuntu Server - techinfoBiT-SHA-256,Ubuntu Server, Server Solution, Linux Hashing, File Integrity, Cryptographic Hash, sha256sum, Hash Generation

    How To Generate SHA-256 Hash From the Command Line on Ubuntu Server

    Cloudflare To Deprecate Auto Minify Feature On August 5, 2024 - techinfoBiT

    Cloudflare To Deprecate Auto Minify Feature On August 5, 2024

    What is Google Analytics 4 and How to Setup GA4 for Websites - techinfoBiT

    What is Google Analytics 4 and How to Setup GA4 for Websites?

    How To Use Multiple SPF Records In A Domain | Merge Multiple SPF Easily - techinfoBiT

    How To Use Multiple SPF Records In A Domain | Merge Multiple SPF Easily

  • Science Space
  • Gadgets
    • Laptop & PCs
    • Mobile Phones
    • Wearables
  • More
    • How-To Guides
    • Reviews
    • Telecom
    • Applications
    • Press Release
No Result
View All Result
Services
techinfoBiT
  • News
  • Startups
  • Tech
    • Internet
    • Security
  • WebMaster
    • All
    • GEO
    • SEO
    • Server & Hosting
    • Tools
    • WordPress
    'Sorry, This File Type Is Not Permitted for Security Reasons' How To Fix This Error in WordPress Website Development-techinfoBiT

    ‘Sorry, This File Type Is Not Permitted for Security Reasons’ How To Fix This Error in WordPress

    What is DMARC Record in DNS and Why It is Important For Email Security-techinfoBiT

    What is DMARC Record in DNS and Why It is Important For Email Security?

    How To Generate SHA-256 Hash From the Command Line on Ubuntu Server - techinfoBiT-SHA-256,Ubuntu Server, Server Solution, Linux Hashing, File Integrity, Cryptographic Hash, sha256sum, Hash Generation

    How To Generate SHA-256 Hash From the Command Line on Ubuntu Server

    Cloudflare To Deprecate Auto Minify Feature On August 5, 2024 - techinfoBiT

    Cloudflare To Deprecate Auto Minify Feature On August 5, 2024

    What is Google Analytics 4 and How to Setup GA4 for Websites - techinfoBiT

    What is Google Analytics 4 and How to Setup GA4 for Websites?

    How To Use Multiple SPF Records In A Domain | Merge Multiple SPF Easily - techinfoBiT

    How To Use Multiple SPF Records In A Domain | Merge Multiple SPF Easily

  • Science Space
  • Gadgets
    • Laptop & PCs
    • Mobile Phones
    • Wearables
  • More
    • How-To Guides
    • Reviews
    • Telecom
    • Applications
    • Press Release
No Result
View All Result
techinfoBiT

How to Deploy a Spring Boot Application with PostgreSQL on Ubuntu 24.04 VPS

Nishant Kumar by Nishant Kumar
June 2, 2026
Reading Time: 27 mins read
Share on FacebookShare on Twitter > XShare via WhatsAppShare on LinkedIn

Deploying a Spring Boot application on a VPS involves more than simply uploading application files to a server. A production deployment typically includes a Java runtime, a PostgreSQL database, a reverse proxy such as Nginx, SSL certificates, DNS configuration, and a process manager that keeps the application running continuously. Understanding how these components fit together makes deployment significantly easier to troubleshoot and maintain.

This guide is based on a real deployment, a Spring Boot application backed by PostgreSQL, running on an Ubuntu 24.04 VPS with Nginx as a reverse proxy, SSL from Let’s Encrypt, and Cloudflare sitting in front.

You might also like

how to type in Hindi 2026, Hindi typing Windows 11, Google Input Tools alternative, type in Indian languages, Hindi phonetic keyboard Windows, Microsoft Indic Language Input Tool, Gboard Hindi typing-techinfoBiT

How to Type in Hindi and Indian Languages on Any Device in 2026

May 31, 2026
Jio and Azure Partnership- Revolutionising India’s Cloud Landscape - techinfoBiT

Jio and Azure Partnership: Revolutionising India’s Cloud Landscape

July 4, 2025

Along the way, it became clear that a successful deployment depends on much more than simply uploading a JAR file and starting a service. PostgreSQL version compatibility, database ownership, application startup validation, reverse proxy configuration, SSL certificates, and DNS settings all play an important role in the overall deployment process.

These areas are covered throughout the guide, along with practical recommendations to help you avoid some of the most common deployment issues before they occur. One recommendation worth following from the start is to use the same PostgreSQL version that was used to create the database dump, as this can prevent restore and compatibility issues later in the deployment process.

Why a VPS Is the Right Choice for Spring Boot Applications

Spring Boot applications are designed to run as long-lived services. Unlike static websites, they require a Java runtime, dedicated memory allocation, a database server, and a process manager that automatically starts the application when the server boots and restarts it if it fails. A VPS provides complete control over these components while remaining affordable for most production deployments.

Running your own VPS also gives you full control over PostgreSQL versions, Java upgrades, SSL certificates, reverse proxy configuration, firewall rules, backup strategies, and application monitoring. This flexibility becomes particularly important when deploying applications that depend on specific software versions or require custom server configurations.

For most Spring Boot deployments, a VPS with 2 GB of RAM is sufficient to run the application, PostgreSQL, and Nginx comfortably. Larger deployments can scale vertically by increasing server resources or horizontally through load balancing and multiple application instances. If you compare the flexibility, scalability and performance of a VPS, the cost you pay for the VPS subscription is totally justified, though if you get the right VPS service provide adn offer, it would be almost similar to the cost of good shared hosting plans.

What You Need Before You Start

Have the following ready before you begin. Missing any of these will stop you partway through.

  • A fresh Ubuntu 24.04 VPS with SSH access and a root or sudo account
  • A domain name with access to the DNS management panel
  • The compiled application JAR file from your developer (.jar)
  • A PostgreSQL database dump file (.dump or .sql), if the developer has provided one
  • A deployment script (deploy.sh) and README, if the developer has provided them
  • Any API credentials the application requires, payment gateway keys, email service credentials, and so on

If you are setting up a VPS for the first time, Hostinger VPS plans offer up to a 20% discount via this link and are a reliable starting point. If you need a step-by-step walkthrough of the initial VPS setup process, see How to Create and Configure a Hostinger VPS Account.

One rule applies regardless of whether you are using a deployment script or doing everything manually: read the README before running anything. A deployment script can create system users, modify databases, install packages, and start services. Understanding what it does before it does it is not optional; it is what prevents you from spending an hour reversing something the script did that you did not anticipate.

Understanding the Deployment Architecture

Before you install anything, it helps to have a clear mental picture of how the components connect and why each one is there.

Internet
    │
Cloudflare  ← optional: CDN, DDoS protection, proxy layer
    │
Nginx       ← handles ports 80 and 443, SSL termination, reverse proxy
    │
Spring Boot ← Java application on port 8080, internal only
    │
PostgreSQL  ← database on port 5432, local only

The browser communicates with Nginx, not directly with your Spring Boot application. Nginx receives every incoming request on port 80 (HTTP) or port 443 (HTTPS) and forwards it internally to the Java process on port 8080. This means your application never needs to handle SSL itself; Nginx does that work. It also means port 8080 should never be open to the internet; it only needs to be reachable from localhost. Similarly, PostgreSQL listens on port 5432 and is only reachable from the local machine.

Cloudflare is an optional layer you add after everything else is working. It routes traffic through its global network, providing DDoS mitigation, caching, and an additional SSL edge. The reason it goes last is that Cloudflare can interfere with both SSL certificate issuance and initial DNS verification if it is active too early.

Step 1: Prepare the VPS

Connect to your server as root and update the operating system before anything else. Ubuntu ships with packages that can be weeks or months old, and starting from an updated baseline prevents dependency conflicts during installation.

ssh root@YOUR_SERVER_IP
apt update && apt upgrade -y

Next, create a dedicated administrative user. Working directly as root for daily operations is a security risk. An accidental command with root privileges can do irreversible damage. Create a user with sudo access instead:

adduser deploy
usermod -aG sudo deploy

From this point, use the deploy user for all server work. Switch to it immediately with su - deploy, or disconnect and reconnect via SSH as that user. Once you have confirmed the sudo user works correctly, you can optionally disable root SSH login by setting PermitRootLogin no in /etc/ssh/sshd_config and restarting the SSH service.

Step 2: Point Your Domain to the VPS

DNS changes take time to propagate, anywhere from a few minutes to a couple of hours depending on your registrar and TTL settings. Configure DNS early so it is ready by the time you need it for Nginx and SSL.

Log in to your DNS provider and create the following records, pointing both to your VPS IP address:

  • A record — host: @ — value: your VPS IP address
  • A record — host: www — value: your VPS IP address

Alternatively, you can create an A record for the root domain and a CNAME record for www pointing back to the root domain. Either configuration works for this setup.

To confirm propagation from the server itself:

dig yourdomain.com +short

When the command returns your VPS IP address, DNS is live, and you can proceed. Do not attempt SSL certificate installation until this is confirmed. Let’s Encrypt validates domain ownership by making an HTTP request to your domain, which must resolve to your server for validation to succeed.

If you are using Cloudflare, keep the proxy disabled for now. In the Cloudflare DNS panel, your records should show a grey cloud icon (DNS Only), not an orange one (Proxied). Enable the proxy only after SSL is installed and HTTPS is verified working directly on the server.

Step 3: Check the PostgreSQL Dump Version – Do This First

If you have received a database dump file from the developer, check its PostgreSQL version compatibility before running any deployment steps. This five-second check prevented a situation during this deployment that cost over an hour of debugging.

PostgreSQL dump files are only forward-compatible. A dump created on PostgreSQL 18 cannot be restored on a server running PostgreSQL 16. If there is a version mismatch, the restore will fail immediately with this error:

pg_restore: error: unsupported version (1.16) in file header

The number in parentheses refers to the pg_dump file format version, not the PostgreSQL version itself — but the two are linked. PostgreSQL 18 produces file format 1.16, which older servers cannot read. Check the dump’s embedded information:

file yourdump.dump

Then check what version is installed on the server:

psql --version
pg_restore --version

If the versions do not match, upgrade PostgreSQL on the server before proceeding. Ubuntu 24.04 ships with PostgreSQL 16 by default in its standard repositories. To install a newer version, you need to add the official PostgreSQL apt repository:

sudo apt install -y curl ca-certificates
sudo install -d /usr/share/postgresql-common/pgdg
sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc
sudo sh -c 'echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
sudo apt update

Then install the version you need. For PostgreSQL 18:

sudo apt install -y postgresql-18 postgresql-client-18

Verify both the server and the client tools report the correct version before continuing:

psql --version
pg_restore --version

When multiple PostgreSQL versions are installed, you may see the old cluster still present on a different port, for example, PostgreSQL 16 on port 5433 while PostgreSQL 18 runs on the default port 5432. This is expected. What matters is that the active cluster and the client tools both match the dump version.

Step 4: Upload Your Deployment Files to the VPS

Transfer your files from your local machine to the VPS. I recommend using FileZilla to upload the files to the application directory. Or, the scp Command copies an entire directory in one step. The following command must be executed on your local computer from the directory containing the deployment files, not on the VPS itself.

scp -r AppName-path-deployment/ deploy@YOUR_VPS_IP:~/

Then SSH in and confirm everything arrived:

ssh deploy@YOUR_VPS_IP
cd ~/AppName-path-deployment
ls -la

If you are following Method A (deploy.sh), you need the JAR file, the dump file, and the script all in the same directory. The script will exit immediately if the JAR is not found alongside it. If you are following Method B (manual), you just need the JAR and the dump file accessible on the server; the directory location is your choice.

Deployment Method A: Using the deploy.sh Script

If your developer has provided a deploy.sh Script, this is the faster path. The script automates the entire setup of system users, the database, the application directory, the secrets file, and the systemd service. Read through it once to understand what it will do on your server, then run it with:

cd ~/AppName-path-deployment
sudo bash deploy.sh

The script runs through the following stages automatically, pausing only when it needs information from you that it cannot determine on its own.

It starts by locating the JAR file in the same directory. If no JAR is present, it exits immediately; this is intentional. Then it checks for Java 17 and PostgreSQL, installing them if missing. It creates a dedicated Linux system user (in this deployment, AppName) under which the application will run. This is a security measure: the application process runs with minimal privileges and cannot access files outside its designated directory at /opt/AppName.

For the database, the script creates the PostgreSQL user AppName and pauses to ask you for a password — choose a strong one, this is a production credential. It creates the database AppName_path_db owned by that user and grants the correct privileges on the public schema. If a dump file named AppName-data.dump or AppName-data.sql is present and the database is currently empty, the script imports it automatically. It detects the file format by reading the first five bytes: custom binary dumps begin with PGDMP and are processed withpg_restore --no-owner --no-acl; plain SQL files are piped into psql. After the restore, it explicitly grants permissions on all tables and sequences to AppName_user.

You will then be prompted for your application secrets — your domain name, Razorpay API credentials, and whether to enable online payments immediately. The script writes all of this to /opt/AppName/token.properties with file permissions set to 600So only the application’s own system user can read it. No secrets are embedded in the JAR or stored in environment variables visible to other processes. Because this file lives outside the JAR, you can update any value at any time by editing it and restarting the service; no rebuild required.

Finally, the script writes a systemd service file, enables the service to start on boot, starts it, waits four seconds, and confirms the service is active before declaring success.

Important: When the script reports success, do not assume the application is working. Proceed to Step 5 immediately to verify the application is genuinely healthy.

Re-running the Script for Updates

The script is designed to be idempotent — safe to run again on the same server without causing damage. On a re-run, it detects that the system user, database, and token.properties already exist and skips all of those steps. The only thing it does on a re-run is stop the service, replace the JAR with the new version, and restart. Your data and configuration are preserved.

To deploy an update, build the new JAR on the developer machine, place it in the deployment folder alongside deploy.sh, and run the script again:

scp -r AppName-path-deployment/ deploy@YOUR_VPS_IP:~/
ssh deploy@YOUR_VPS_IP "cd ~/AppName-path-deployment && sudo bash deploy.sh"

Deployment Method B: Manual Deployment Without deploy.sh

If your developer has not provided a deployment script, the process is entirely manual but follows the same logical order. Work through each section below in sequence.

Install Java and PostgreSQL

Install the Java runtime. Most Spring Boot applications require Java 17 — confirm the version with your developer before installing.

sudo apt update
sudo apt install openjdk-17-jre-headless -y
java -version

Install PostgreSQL. If the dump version check in Step 3 showed you need a specific version, install that version via the official PostgreSQL apt repository as described in Step 3. Otherwise, the Ubuntu default is appropriate for a fresh database with no existing dump to restore:

sudo apt install postgresql postgresql-contrib -y
sudo systemctl enable postgresql
sudo systemctl start postgresql
sudo systemctl status postgresql

Create the Database User and Database

Connect to PostgreSQL as the built-in superuser and create a dedicated user and database for your application. Replace appuser, StrongPassword123, and appdb with the names specified by your developer:

sudo -u postgres psql

Inside the PostgreSQL prompt, run these commands one by one:

CREATE USER appuser WITH PASSWORD 'StrongPassword123' NOSUPERUSER NOCREATEDB NOCREATEROLE LOGIN;
CREATE DATABASE appdb OWNER appuser;
GRANT ALL PRIVILEGES ON DATABASE appdb TO appuser;
GRANT ALL ON SCHEMA public TO appuser;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO appuser;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO appuser;
\q

Restore the Database Dump

If a dump file was provided, import it now. The correct command depends on the dump format. For a custom binary dump (the most common format, recognisable by the .dump extension):

sudo -u postgres pg_restore --no-owner --no-acl -d appdb /path/to/database.dump

For a plain SQL dump (a .sql text file):

sudo -u postgres psql -d appdb -f /path/to/database.sql

After the restore, transfer ownership of all restored objects to your application user. This step is critical — during the restore, all tables are created by the postgres superuser, which makes postgres the owner of every table. If your application tries to run schema migrations (adding or altering columns), it will fail with a permissions error unless it owns the tables. Run these commands while connected to the correct database:

sudo -u postgres psql -d appdb
REASSIGN OWNED BY postgres TO appuser;
GRANT ALL ON ALL TABLES IN SCHEMA public TO appuser;
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO appuser;
\q

Verify the tables were imported with the correct owner:

sudo -u postgres psql -d appdb -c "\dt+"

Create the Application System User and Directory

Create a dedicated system user to run the application. This user has no login shell and no home directory — it exists only to own the process:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin appuser

Create the application directory and copy the JAR into it:

sudo mkdir -p /opt/myapp
sudo cp application.jar /opt/myapp/application.jar
sudo chown -R appuser:appuser /opt/myapp

Create the Configuration File

Many Spring Boot applications are built to load a separate secrets file at runtime so that credentials never need to be baked into the JAR or rebuilt when values change. Check your developer’s documentation or any token.properties.template file they have provided to see what keys your specific application expects. For applications built to load from a file at the working directory, create the file now:

sudo nano /opt/myapp/token.properties

A typical configuration file looks like this — use the exact key names your developer has specified:

app.base-url=https://yourdomain.com
spring.datasource.username=appuser
spring.datasource.password=StrongPassword123
app.payment.online-enabled=false
razorpay.key.id=rzp_test_xxxxxxxxxxxx
razorpay.key.secret=xxxxxxxxxxxxxxxxxxxx

If your application does not use a custom secrets file and instead relies on environment variables or a standard application.properties, follow the developer’s documentation for the correct approach. If you need to pass a config file to a standard Spring Boot application that does not load one automatically, you can supply it at startup using the --spring.config.additional-location flag — see the systemd service section below.

Lock down the file permissions so only the application user can read it:

sudo chmod 600 /opt/myapp/token.properties
sudo chown appuser:appuser /opt/myapp/token.properties

Create the systemd Service

Create a systemd service file so the application runs as a managed background service, starts automatically on boot, and restarts on failure:

sudo nano /etc/systemd/system/myapp.service

Paste the following, adjusting the paths, the user name, and the service identifier to match your application:

[Unit]
Description=Spring Boot Application
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/java \
    -Xms256m -Xmx512m \
    -jar /opt/myapp/application.jar \
    --spring.profiles.active=prod
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/myapp

[Install]
WantedBy=multi-user.target

A few notes on this configuration. The After=postgresql.service and Requires=postgresql.service lines ensure the database is running before the application attempts to start. The -Xms256m -Xmx512m flags set the minimum and maximum JVM heap size — adjust these based on your available VPS memory. The hardening options at the bottom (NoNewPrivileges, PrivateTmp, ProtectSystem, ReadWritePaths) restrict what the application process can do on the rest of the server, which is good practice for any long-running service. If your application needs to write to a directory outside /opt/myapp — for uploaded files, for example — add that path to ReadWritePaths.

If your application does not auto-load a custom config file and you need to pass one explicitly, add the following to the ExecStart line:

--spring.config.additional-location=file:/opt/myapp/token.properties

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp

Step 5: Verify the Application Is Actually Running

This is the most important step in the entire process, and the one most commonly skipped. Whether you used the deployment script or the manual path, verify the application is genuinely healthy before proceeding to Nginx. Do not move forward until this is confirmed.

A systemd service showing “active (running)” does not mean the application is healthy — it means the process started. Spring Boot applications take several seconds to fully initialise, and some failure modes cause the application to start, encounter an error, and restart in a loop that looks healthy from the outside.

The definitive check is whether the application is listening on port 8080. Replace YOUR_SERVICE with your actual service name — AppName If you used deploy.sh, or myapp If you used the manual path:

sudo systemctl status YOUR_SERVICE
ss -tulpn | grep 8080

If the ss command returns output showing a Java process bound to *:8080, The application is running and accepting connections. If it returns nothing, the application has crashed or has not finished starting.

When port 8080 is silent, read the logs immediately:

journalctl -u YOUR_SERVICE -n 100 --no-pager

The logs will tell you exactly what went wrong. The two most common causes at this stage are a database connectivity problem (wrong credentials or PostgreSQL not running) and a table ownership error from the database restore. The ownership error looks like this in the logs:

ERROR: must be owner of table students

This happens because the application is trying to run a schema migration, adding or modifying a column, and it does not own the table it is trying to alter. In PostgreSQL, owning a table and having query privileges on it are two separate things. An application user can have full SELECT, INSERT, UPDATE, and DELETE rights on a table and still be unable to run ALTER TABLE on it without ownership. If you followed the deploy.sh path and hit this error: the post-restore GRANT step in the script did not transfer ownership correctly. Fix it by running the following commands — replace appdb with your actual database name and appuser With your application’s database user:

sudo -u postgres psql -d appdb
REASSIGN OWNED BY postgres TO appuser;
GRANT ALL ON ALL TABLES IN SCHEMA public TO appuser;
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO appuser;
\q

Restart the service and check the port again:

sudo systemctl restart YOUR_SERVICE
sleep 5
ss -tulpn | grep 8080

Once port 8080 is listening, make a local HTTP request to confirm the application responds:

curl -I http://127.0.0.1:8080

An HTTP/1.1 200 or HTTP/1.1 302 response confirms the application is healthy. Only now should you move on to Nginx. The most common deployment mistake is skipping this verification and spending an hour troubleshooting Nginx or DNS when the actual problem is that the application never successfully started.


Step 6: Install and Configure Nginx

Nginx acts as the reverse proxy between the internet and your Spring Boot application. Unlike a PHP setup, there is no PHP-FPM, no FastCGI bridge, and no special module needed. The entire job of Nginx here is to receive incoming HTTP and HTTPS requests and forward them to port 8080.

sudo apt install nginx -y

Create a new configuration file for your site. Ubuntu stores site configurations in /etc/nginx/sites-available/ and enables them via symlinks in /etc/nginx/sites-enabled/:

sudo nano /etc/nginx/sites-available/myapp

Paste the following, replacing yourdomain.com with your actual domain:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Each of those proxy header lines serves a purpose. Host passes the original domain name so the application knows which site is being requested. X-Real-IP carries the visitor’s actual IP address — without it, every request would appear to come from 127.0.0.1. X-Forwarded-For appends the visitor’s IP to a chain, which matters when traffic passes through Cloudflare or other proxies. X-Forwarded-Proto tells the application whether the original request arrived over HTTP or HTTPS — your application needs this to generate correct redirect URLs and enforce HTTPS properly.

Enable the configuration, remove the default Nginx welcome page, test, and reload:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Open a browser and navigate to http://yourdomain.com. You should see your application. If the Nginx default welcome page appears instead, the symlink was not created or Nginx was not reloaded — confirm with ls /etc/nginx/sites-enabled/ that your file is listed and the default is gone. If you see a 502 Bad Gateway error, the application is not running on port 8080 — return to Step 5.


Step 7: Install SSL with Let’s Encrypt

With the application accessible over HTTP, add HTTPS. Certbot handles the entire process: it validates your domain, obtains a free certificate from Let’s Encrypt, updates your Nginx configuration to serve HTTPS, and configures automatic renewal.

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot will ask for your email address for renewal notifications and ask you to accept the terms of service. It then contacts the Let’s Encrypt servers, which make an HTTP request to your domain to verify you control it. This is why DNS must be propagated and Nginx must be serving your site before this step. If Cloudflare’s proxy is active at this point, it can intercept the validation request and cause it to fail — which is another reason to keep Cloudflare in DNS Only mode until after certificate issuance.

After the certificate is issued, Certbot automatically modifies your Nginx configuration to listen on port 443 with the certificate in place and to redirect all HTTP traffic to HTTPS. Reload Nginx and confirm HTTPS works in a browser:

sudo systemctl reload nginx

Let’s Encrypt certificates are valid for 90 days. Certbot installs a systemd timer that handles renewal automatically. Verify it is active and test the renewal process with a dry run:

certbot certificates
sudo systemctl status certbot.timer
sudo certbot renew --dry-run

Step 8: Configure Cloudflare

If you are using Cloudflare, this is the moment to enable it. Log in to the Cloudflare dashboard, go to your domain’s DNS settings, and switch the records for your root domain and www from DNS Only (grey cloud) to Proxied (orange cloud). From this point, all traffic routes through Cloudflare’s network before reaching your server.

The single most important setting to configure is the SSL mode. Navigate to SSL/TLS → Overview and set it to Full (Strict). Here is why the other options cause problems: Cloudflare’s default Flexible mode establishes an encrypted connection between the browser and Cloudflare, but then sends traffic to your origin server over plain HTTP.

If your Nginx configuration forces HTTPS redirects, which it now does, thanks to Certbot, this creates an infinite redirect loop. The browser gets redirected to HTTPS, Cloudflare requests your server over HTTP, your server redirects to HTTPS again, and the cycle repeats. Full (Strict) requires a valid certificate on the origin server (which you now have) and keeps the entire connection encrypted end-to-end.

Decide on your canonical domain, will yourdomain.com or www.yourdomain.com be the primary address? Choose one and configure Cloudflare redirect rules to send all traffic from the other version to it. This deployment uses the non-www version as canonical. Mixing the two without a consistent redirect creates duplicate content issues that affect search engine rankings over time.

Managing the Application Day-to-Day

The application runs as a systemd service, so all standard systemd commands apply. In all commands below, replace YOUR_SERVICE with your actual service name, AppName for the deploy.sh path, myapp for the manual path, or whatever name you chose.

# Check the current service status
sudo systemctl status YOUR_SERVICE

# Follow live logs as the application handles requests
journalctl -u YOUR_SERVICE -f

# View the last 100 log lines without following
journalctl -u YOUR_SERVICE -n 100 --no-pager

# Restart the service — required after editing the config file
sudo systemctl restart YOUR_SERVICE

# Verify the application is listening on port 8080
ss -tulpn | grep 8080

# Quick local health check
curl -I http://127.0.0.1:8080

To update a configuration value — switching from Razorpay test keys to live keys, changing the domain, or rotating the database password- edit the secrets file and restart the service. No JAR rebuild is needed:

sudo nano /opt/YOUR_APP_DIR/token.properties
sudo systemctl restart YOUR_SERVICE

To connect directly to the database for inspection or manual queries:

sudo -u postgres psql -d YOUR_DATABASE_NAME

When a new version of the application is available, the workflow is the same as the original deployment: receive the new JAR from your developer, place it alongside deploy.sh, and run the script again. For the manual path, stop the service, replace the JAR, and restart:

sudo systemctl stop YOUR_SERVICE
sudo cp new-application.jar /opt/YOUR_APP_DIR/application.jar
sudo chown YOUR_APP_USER:YOUR_APP_USER /opt/YOUR_APP_DIR/application.jar
sudo systemctl start YOUR_SERVICE

Setting Up Automated Backups

A backup strategy must be in place before the application goes live. The simplest reliable approach is a scheduled daily pg_dump written to a local file, combined with offsite storage. The examples below use the deploy.sh paths — replace /opt/AppName, AppName, and AppName_path_db With your own paths and database name if you followed the manual path.

Create a directory for backup files and create the backup script:

sudo mkdir -p /opt/AppName/backups
sudo nano /opt/AppName/backup.sh

Paste the following into the script file:

#!/bin/bash
BACKUP_DIR="/opt/AppName/backups"
DATE=$(date +%Y%m%d_%H%M%S)
DUMP_FILE="${BACKUP_DIR}/AppName_path_db_${DATE}.dump"

/usr/bin/sudo -u postgres /usr/bin/pg_dump -Fc AppName_path_db > "${DUMP_FILE}"

# Keep only the last 7 daily backups
ls -tp "${BACKUP_DIR}"/*.dump 2>/dev/null | tail -n +8 | xargs -I {} rm -- {}

Note that full paths are used for both sudo and pg_dump. When a script runs from the system cron, the PATH environment is minimal and commands like pg_dump may not be found unless you specify the full path. Using /usr/bin/pg_dump eliminates this uncertainty.

Make the script executable and schedule it to run nightly at 2 AM via the root crontab:

sudo chmod +x /opt/AppName/backup.sh
sudo crontab -e

Add this line at the end of the crontab file:

0 2 * * * /opt/AppName/backup.sh

The -Fc flag produces a compressed custom binary dump, the same format used in the original deployment package, compact and suitable for selective table restore if needed.

Storing backups only on the same VPS is not a real backup strategy. A disk failure, ransomware attack, or accidental deletion would take your data and your backups at the same time. Use rsync, rclone, or your hosting provider’s object storage to copy dump files to a second location automatically after each run.

Troubleshooting Reference or FAQs:

– pg_restore fails with “unsupported version in file header”

The dump was created on a newer PostgreSQL version than the one installed on your server. Run file yourdump.dump to see the dump’s format version, identify which PostgreSQL version produced it, and upgrade PostgreSQL on the server to match before attempting the restore again. See Step 3 for the upgrade procedure.

– The service is “active (running)” but nothing is listening on port 8080

The application started and then crashed. Run journalctl -u YOUR_SERVICE -n 50 --no-pager immediately and read the error. The most common causes are incorrect database credentials in the config file, PostgreSQL not running, or the table ownership problem described below.

– The logs show “must be owner of table [tablename]”

The application is trying to run a schema migration and does not own the tables. This happens because pg_restore runs as the postgres superuser, so all restored tables are owned by postgres rather than the application database user. Connect to the database as postgres and run REASSIGN OWNED BY postgres TO appuser; followed by GRANT ALL ON ALL TABLES IN SCHEMA public TO appuser;. Then restart the service. See Step 5 for the full commands.

– Nginx returns a 502 Bad Gateway error

Nginx is running, and your configuration is correct, but it cannot reach the application on port 8080 because the application is not running. Verify with ss -tulpn | grep 8080 and resolve the application startup error before returning to Nginx. Do not spend time checking Nginx configuration when the application itself is the problem.

– Nginx shows the default welcome page instead of the application

The default Nginx site is still active. Remove it with sudo rm /etc/nginx/sites-enabled/default, confirm your configuration file is linked with ls /etc/nginx/sites-enabled/, and reload Nginx with sudo systemctl reload nginx.

– Certbot fails to issue an SSL certificate

Either DNS has not propagated yet, Nginx is not serving the correct site on port 80, or Cloudflare’s proxy is active and intercepting the Let’s Encrypt HTTP challenge. Confirm DNS propagation with dig yourdomain.com +short, test that your site loads with curl -I http://yourdomain.com, and ensure Cloudflare is in DNS Only mode before running Certbot.

– HTTPS works, but the browser loops on redirects

Cloudflare’s SSL mode is set to Flexible. Navigate to SSL/TLS → Overview in the Cloudflare dashboard and switch it to Full (Strict). Flexible mode sends requests from Cloudflare to your origin over HTTP, which triggers the HTTPS redirect on Nginx, which Cloudflare then requests over HTTP again — an infinite loop. Full (Strict) eliminates this by requiring encrypted communication all the way to the origin.

The application shows the wrong domain in URLs or mixed content warnings

The base URL configured in your application’s secrets file does not match the domain it is being served from, or the X-Forwarded-Proto header is not being passed correctly through Nginx. Verify the app.base-url value in your config file matches the domain exactly (including the https:// prefix), confirm the proxy_set_header X-Forwarded-Proto $scheme; line is present in your Nginx configuration, and restart the service after making any changes.

Final Thoughts

Deploying a Spring Boot application is not inherently more difficult than any other kind of server deployment; it just involves a different set of components and a different mental model. The key discipline is validating each layer in order before moving to the next. Confirm the database is running and correctly set up. Confirm the application starts and responds on port 8080. Only then configure Nginx. Only then add SSL. Only then enable Cloudflare. Each layer depends on the one below it, and when something goes wrong, this sequence tells you exactly where to look.

The two real-world problems documented in this guide, the PostgreSQL version mismatch and the table ownership issue after a restore, are common enough that they are worth knowing by name. The version check is five seconds. The ownership fix is three SQL commands. Neither requires deep database expertise once you understand the mechanism.

A dump file carries the version it was created on, and pg_restore cannot read ahead. The PostgreSQL superuser creates objects it restores, so your application user never inherits ownership automatically. These are not obscure edge cases; they are predictable outcomes of how PostgreSQL works, and knowing them turns a two-hour debugging session into a ten-minute resolution.

Tags: CloudFlareJava Application DeploymentJava DeploymentLet's EncryptPostgreSQLPostgreSQL HostingSpring BootSpring Boot DeploymentUbuntu 24.04Ubuntu VPS SetupVPS
ShareTweetSendShare
Previous Post

How to Type in Hindi and Indian Languages on Any Device in 2026

Nishant Kumar

Nishant Kumar

Nishant is a passionate tech blogger and has been writing about technology since 2007. His insatiable love for gadgets has made him closely follow the advancements & innovations our society has made in terms of technology since a long while now. Nishant is a highly sought after reviewer with many manufacturers requesting his opinions about their products. He covers Mobile Phones, Gadgets, Tools and all kind of tech products to give consumers Genuine Reviews, Buying Guides and reliable news.

Related Articles

how to type in Hindi 2026, Hindi typing Windows 11, Google Input Tools alternative, type in Indian languages, Hindi phonetic keyboard Windows, Microsoft Indic Language Input Tool, Gboard Hindi typing-techinfoBiT

How to Type in Hindi and Indian Languages on Any Device in 2026

by techinfoBiT
May 31, 2026
0

If you have been searching for the Google IME offline installer lately, you already know the bad news. Google quietly discontinued the Windows installer years ago. The download links are...

Jio and Azure Partnership- Revolutionising India’s Cloud Landscape - techinfoBiT

Jio and Azure Partnership: Revolutionising India’s Cloud Landscape

by Nishant Kumar
July 4, 2025
0

The collaboration of Reliance Jio and Microsoft Azure was indeed a landmark for the Indian cloud ecosystem. Reliance Jio, the telecom giant under Reliance Industries Limited, partnered with Microsoft in...

Ubuntu Server How To Find Largest File In Directory Recursively - techinfoBiT

Ubuntu Server: How To Find Largest File In Directory Recursively

by Nishant Kumar
March 24, 2025
0

When managing an Ubuntu Server, disk space can quickly become an issue, especially if large files accumulate unnoticed. If you need to identify the biggest file within a directory (including...

Buying an Affordable & High-Performance VPS or Web Hosting-Best VPS Offer-Hostinger-techinfoBiT Blog For-Best WordPress Hosting

Buying an Affordable & High-Performance VPS or Web Hosting

by Nishant Kumar
March 9, 2025
0

The Virtual Private Server (VPS) is now becoming a mainstream and popular hosting option for all kinds of websites and applications. The market share of Virtual Private Servers (VPS) has...

Load More

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.

techinfoBiT | Startup & Tech News, Reviews | WebMaster, How-To Guides-Tech Blog-Startup Blog India

techinfoBiT is your go-to source for the latest startup & tech news, technology reviews; WebMaster, SEO tips and how-to guides to help you stay updated and navigate the digital world know more

  • About
  • Contacts
  • Disclaimer
  • Privacy Policy

© 2012-2025 techinfoBiT | All Rights Reserved

No Result
View All Result
  • News
  • Startups
  • Tech
  • WebMaster
  • Gadgets
  • How-To Guides
  • Science Space
  • Services

© 2012-2025 techinfoBiT | All Rights Reserved