How to Host Spring Boot on VPS: Production Guide (2026)
Transforming a local Spring Boot application into a production-ready system deployed on a Virtual Private Server (VPS) with enterprise-grade security, reverse proxying, rate limiting, and SSL.
1. Why Do We Need a VPS? (Deployment Options Compared)
When moving a Spring Boot application out of a local development environment (localhost), developers face several architectural deployment choices. Understanding the trade-offs helps clarify why a VPS is the gold standard for full control and cost-effective production deployment.
2. Core Mental Model: The VPS Analogy
Real-Life Analogy: Think of a VPS like renting an independent apartment in a secure residential building.
- Shared Hosting is like sharing a single room with 1,000 other people where everyone fights for the same desk (CPU), closet space (Storage), and Wi-Fi bandwidth. If someone downloads a massive file, your connection crawls. You also cannot install your own locks or furniture.
- VPS (Virtual Private Server) is like having your own dedicated apartment within a shared physical building. The building shares the physical power plant, but your flat has dedicated resources (RAM, CPU, Disk, Bandwidth) and root access. You have the keys, you can paint the walls, install custom software, and set up your own security rules.
3. VPS Provisioning & OS Setup
Selecting a reliable provider (such as Hostinger KVM VPS) and provisioning a clean Linux environment.
Steps to Provision and Configure the VPS:
- Choose Provider & Specs: Select a cost-effective plan (e.g., 1 Core CPU, 4 GB RAM, 50 GB NVMe Disk, 4 TB Bandwidth).
- Select Data Center Location: Choose a region geographically closest to your target audience (e.g., India/Mumbai) to minimize network latency.
- Operating System Selection: Choose Plain OS (Ubuntu 24.04 LTS) rather than pre-configured panels or app templates. This guarantees zero bloatware and full root control.
- Authentication Setup: Configure a strong Root Password containing uppercase/lowercase letters, numbers, and special characters. (SSH Keys can be added later).
4. Connecting via SSH & System Initialization
Once the VPS is provisioned, you connect securely from your local terminal using SSH (Secure Shell).
The SSH Connection Flow
Local Developer Terminal (Mac/Linux/Windows PowerShell)
|
| ssh root@<YOUR_VPS_IP> (Encrypted SSH Protocol over Port 22)
v
Hostinger VPS (Remote Linux Environment)
Commands to Run Initial Checks & Install Java
# Connect to your VPS
ssh root@your_vps_ip
# Check system architecture and kernel
uname -a
# Update Ubuntu package repository index
sudo apt update
sudo apt upgrade -y
# Install Java Development Kit (JDK 21 / 17 / 8 as required)
sudo apt install openjdk-21-jdk -y
# Verify Java installation
java -version
Setting up JAVA_HOME Environment Variable
To ensure system services can locate the Java executable, configure JAVA_HOME:
# Add JAVA_HOME to /etc/environment
echo "JAVA_HOME=\"/usr/lib/jvm/java-21-openjdk-amd64\"" | sudo tee -a /etc/environment
source /etc/environment
# Verify JAVA_HOME path
echo $JAVA_HOME
5. Preparing & Uploading the Spring Boot Jar
Instead of hardcoding environment configuration inside application.properties, externalize secrets using a .env file on the production server.
Local Build & Packaging
Run the Maven build locally to package your application into an executable .jar file, skipping tests for speed:
mvn clean package -DskipTests
The compiled jar will be located at target/your-app-name.jar.
Secure File Transfer (scp)
Transfer both your executable jar and your production .env configuration file to a dedicated directory on the VPS:
# Create application directory on VPS
ssh root@your_vps_ip "mkdir -p /root/general-app"
# Upload Jar file from local machine
scp target/general-app.jar root@your_vps_ip:/root/general-app/
# Upload Environment variables file from local machine
scp .env root@your_vps_ip:/root/general-app/
6. Running Spring Boot as a SystemD Production Service
If you run java -jar directly in the terminal, closing the SSH connection or pressing Ctrl + C terminates the process. Production systems require SystemD to manage the lifecycle, handle automatic restarts on crashes, and log output.
Creating the SystemD Service File
Create a new service configuration file in /etc/systemd/system/general-app.service:
nano /etc/systemd/system/general-app.service
Paste the following production configuration:
[Unit]
Description=General Spring Boot Production Application
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/root/general-app
EnvironmentFile=/root/general-app/.env
ExecStart=/usr/bin/java -jar /root/general-app/general-app.jar
Restart=always
RestartSec=10
StandardOutput=file:/var/log/general-app.out.log
StandardError=file:/var/log/general-app.err.log
[Install]
WantedBy=multi-user.target
Activating and Starting the Service
Run the following commands to register, enable, and start your SystemD service:
# Notify SystemD of the new configuration file
sudo systemctl daemon-reload
# Enable service to start automatically on system boot
sudo systemctl enable general-app.service
# Start the service immediately
sudo systemctl start general-app.service
# Check live service status
sudo systemctl status general-app.service
7. Nginx Reverse Proxy Setup
Spring Boot applications typically run on internal ports like 8080 or 8081. Exposing raw backend ports directly to the public web is unprofessional and unsecure. We use Nginx as a Reverse Proxy to receive traffic on standard web ports (80 and 443) and forward them internally.
The Nginx Reverse Proxy Flow
Client Browser (Internet)
|
| HTTP Request (Port 80 / HTTPS 443)
v
Nginx Web Server (Reverse Proxy)
|
| Internal HTTP Forwarding (Port 8081)
v
Spring Boot Application (Embedded Tomcat)
Installing and Configuring Nginx
# Install Nginx
sudo apt install nginx -y
# Create Nginx server block configuration for your app
sudo nano /etc/nginx/sites-available/general-app
Add the following site configuration:
server {
listen 80;
server_name your_domain_or_ip;
location / {
proxy_pass http://localhost:8081;
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;
}
}
Enable the configuration by creating a symlink in sites-enabled:
sudo ln -s /etc/nginx/sites-available/general-app /etc/nginx/sites-enabled/
# Test Nginx syntax
sudo nginx -t
# Restart Nginx
sudo systemctl restart nginx
8. VPS Firewall Configuration (UFW)
Real-Life Analogy: Think of your VPS ports like rooms in a house. Without a firewall, every door and window is wide open to intruders. UFW (Uncomplicated Firewall) acts as a security guard at the gate, allowing only authorized entry points (SSH, HTTP, HTTPS) while blocking everything else.
Configuring UFW Rules
# Allow SSH connections so you don't lock yourself out
sudo ufw allow 22/tcp
# Allow standard HTTP traffic for Nginx
sudo ufw allow 80/tcp
# Allow standard HTTPS traffic for future SSL certificates
sudo ufw allow 443/tcp
# Set default policies (Deny all incoming, allow all outgoing)
sudo ufw default deny incoming
sudo ufw default outgoing
# Enable the firewall
sudo ufw enable
# Check firewall status
sudo ufw status verbose
Security Note: Port
8081(where Spring Boot runs) is intentionally left unallowed in UFW. External clients cannot bypass Nginx to hit Spring Boot directly.
9. Rate Limiting to Prevent DDoS Attacks
Exposing public APIs without rate limiting leaves your server vulnerable to Denial of Service (DoS) attacks, brute-force requests, and inflated cloud infrastructure bills.
Adding Rate Limiting to Nginx
Open your Nginx configuration file (/etc/nginx/sites-available/general-app or /etc/nginx/nginx.conf) and add a rate-limiting zone:
# Define limit zone: 10 megabytes of memory tracking IP addresses, max 100 requests per minute
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/m;
server {
listen 80;
server_name your_domain_or_ip;
location / {
# Apply rate limiting with a burst buffer of 20 requests
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://localhost:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
If a client exceeds the limit (e.g., sending >100 requests per minute), Nginx automatically responds with an HTTP status code 429 Too Many Requests.
Test and reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
10. Securing with SSL (Let's Encrypt & Certbot)
Running applications over plain HTTP transmits data in clear text, making credentials vulnerable to packet sniffing (e.g., via public Wi-Fi). We secure our VPS using free SSL/TLS certificates from Let's Encrypt.
Installing Certbot & Generating SSL Certificates
# Install Certbot and the Nginx plugin
sudo apt install certbot python3-certbot-nginx -y
# Run Certbot to automatically obtain and configure SSL for your domain
sudo certbot --nginx -d your_domain_name.com
Certbot will automatically:
- Verify domain ownership with Let's Encrypt servers.
- Generate SSL/TLS certificates and store them in
/etc/letsencrypt/live/your_domain_name.com/. - Modify your Nginx configuration to listen on port
443with secure ciphers. - Set up an automatic HTTP-to-HTTPS redirect rule.
11. Summary and Key Takeaways
- VPS Hosting Value: Hosting your Spring Boot application on a VPS provides complete root control, dedicated hardware resources, and stands out on technical resumes.
- Plain OS Control: Provisioning Ubuntu 24.04 LTS without pre-built control panels ensures zero resource bloat and gives you full mastery over the Linux environment.
- Process Management with SystemD: Running Java via SystemD ensures automatic recovery on server restarts, crash resilience, and background process management.
- Nginx as a Reverse Proxy: Nginx acts as the secure public gateway on port
80/443, forwarding traffic internally to Spring Boot running on private ports like8081. - Zero Direct Exposure: Keeping backend application ports closed via UFW firewall prevents external clients from bypassing Nginx security filters.
- API Protection: Implementing Nginx rate limiting (
limit_req_zone) protects your application and cloud bandwidth from DDoS and scraping abuse. - Transport Security: Free SSL certificates via Certbot (
Let's Encrypt) encrypt all data in transit and enforce automatic HTTPS redirects.