Deploy your Java REST API to a VPS - installing the JDK, running as a systemd service, configuring nginx as a reverse proxy, and HTTPS with Let's Encrypt.
Deploying to a VPS with Nginx
You have a working REST API. Now let us put it on the internet. This chapter covers deploying your Java application to a VPS with nginx as a reverse proxy and HTTPS via Let's Encrypt.
The deployment architecture:
Nginx handles the public-facing connection (HTTPS, compression, rate limiting). It forwards API requests to your Java
process running on localhost:8080. This is called a reverse proxy.
Prerequisites
Before starting, you need:
- A VPS running Ubuntu 22.04 LTS (or newer) with SSH access
- A non-root user with sudo privileges
- A firewall configured to allow SSH, HTTP, and HTTPS
- Your task-api.jar file from the previous chapter
- Optionally, a domain name pointed to your server's IP
If you have not set up a VPS before, the initial server setup (creating a user, SSH keys, firewall) is covered in the JavaScript guide's deployment chapter. The steps are identical - follow that guide through Step 2, then return here.
Step 1: install Java on the server
SSH into your server:
Install the JDK:
We install openjdk-21-jre-headless (not the full JDK) - the server only needs to run Java, not compile it. This
is a smaller installation.
Verify:
Result:
Step 2: upload your JAR
Create a directory for the application:
From your local machine, upload the JAR:
Or with scp:
Verify on the server:
Result:
Test the JAR on the server
Open another SSH session and test:
Result:
Stop the server with Ctrl+C. It works - now let us run it properly as a service.
Step 3: create a systemd service
A systemd service runs your Java process in the background, starts it on boot, and restarts it if it crashes.
Create the service file:
Paste this configuration:
Key settings:
User=deploy- runs as the deploy user, not rootRestart=on-failure- automatically restarts on crashesWorkingDirectory- sets the working directory sotasks.datis stored in/opt/task-api/ReadWritePaths- allows writing only to the app directory (systemd security)JAVA_OPTS- JVM memory settings (64MB initial, 256MB max - more than enough for this app)
Enable and start the service
Result:
Viewing logs
Service management commands
Step 4: install and configure nginx
Create the nginx configuration:
Replace YOUR_DOMAIN_OR_IP with your domain or server IP.
Enable the site
Result:
Test through nginx
Result:
The request flows: client → nginx (port 80) → Java API (port 8080) → response back.
Step 5: HTTPS with Let's Encrypt
Install Certbot:
Obtain a certificate (requires a domain name):
Certbot will:
- Verify domain ownership
- Obtain and install the certificate
- Update nginx config for HTTPS
- Set up HTTP → HTTPS redirect
Test:
Result:
Automatic renewal
Certbot sets up a systemd timer for automatic renewal every 90 days.
Step 6: restrict the Java server to localhost
Since nginx handles all public traffic, the Java server should only accept connections from localhost. Update
ApiServer.java:
Rebuild, upload, and restart:
Now port 8080 is not accessible from the outside - only nginx can reach it.
Deploying updates
Whenever you update your code:
Deploy script
Create deploy.sh on your local machine:
Monitoring
Check if the service is running
View application logs
Check resource usage
Health check endpoint
The /api/health endpoint we built lets you verify the API is working. Monitoring services like UptimeRobot or
Healthchecks.io can hit this endpoint periodically and alert you if it goes down.
Security checklist
Review the full server hardening steps from the JavaScript guide's deployment chapter - the same steps apply here:
- Non-root user with sudo
- SSH key authentication (password auth disabled)
- Firewall (
ufw) allowing only SSH, HTTP, HTTPS - HTTPS with Let's Encrypt
- Automatic security updates (
unattended-upgrades) - Fail2Ban for brute-force protection
- Java server bound to
127.0.0.1(not accessible publicly) - systemd service with
NoNewPrivilegesandProtectSystem
Complete deployment summary
What comes next
You now have a Java REST API running in production. The next two chapters cover build tools that make managing dependencies, compiling, and packaging much easier:
- Maven - the most widely used Java build tool, with Gson for JSON
- Gradle - the modern alternative, with Jackson for JSON
Beyond that, you could:
- Add a database - SQLite for simple apps, PostgreSQL for production
- Use a framework - Spring Boot, Quarkus, or Micronaut for more features
- Add authentication - JWT tokens or session-based auth
- Containerize with Docker - consistent deployments across environments
- Set up CI/CD - automatic builds and deploys on git push
- Add logging - structured logging with SLF4J and Logback
- Write tests - JUnit for unit tests, integration tests for API endpoints
Summary
- Install
openjdk-21-jre-headlesson the server (runtime only, no compiler needed). - systemd manages the Java process - start on boot, restart on crash, centralized logging.
- nginx acts as a reverse proxy - handles HTTPS, forwards requests to
localhost:8080. - Let's Encrypt provides free HTTPS certificates with automatic renewal.
- Bind the Java server to
127.0.0.1so it is only reachable through nginx. - Deploy with a simple
rsync+systemctl restartworkflow. - Monitor with
journalctland a/api/healthendpoint.
Your REST API is live. Continue to Maven and Gradle to learn how build tools manage dependencies, simplify packaging, and improve the API project with proper JSON libraries.