TL;DR
Complete guide to running Spring Boot behind nginx with reverse proxy, SSL termination, and static asset serving.
Key facts
- Topology
- Spring Boot behind nginx
TL;DR
Running Spring Boot behind nginx gives you SSL termination, static asset serving, connection buffering, and request rate limiting — none of which the embedded Tomcat handles as efficiently. nginx accepts client connections, terminates TLS, serves static files directly, and proxies dynamic requests to the Spring Boot JAR running on port 8080.
Architecture
Client → nginx (443/SSL) → Spring Boot (8080/HTTP)
nginx handles slow clients, TLS handshakes, and static assets. Spring Boot handles application logic. This separation lets you restart the Java process without dropping SSL connections and lets nginx serve cached responses while the JVM starts up.
Step 1 — Build the fat JAR
Package your Spring Boot application as an executable JAR:
./mvnw clean package -DskipTests
# or with Gradle:
./gradlew bootJar
The JAR includes an embedded Tomcat server and all dependencies. Copy it to the server:
scp target/myapp-1.0.0.jar deploy@server:/opt/springapp/app.jar
Step 2 — Systemd service for Spring Boot
Create /etc/systemd/system/springapp.service:
[Unit]
Description=Spring Boot Application
After=network.target
[Service]
User=springapp
Group=springapp
WorkingDirectory=/opt/springapp
ExecStart=/usr/bin/java \
-Xms512m -Xmx1024m \
-XX:+UseG1GC \
-Dspring.profiles.active=production \
-Dserver.port=8080 \
-jar app.jar
Restart=always
RestartSec=10
SuccessExitStatus=143
StandardOutput=journal
StandardError=journal
PrivateTmp=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
sudo useradd -r -s /bin/false springapp
sudo mkdir -p /opt/springapp
sudo chown springapp:springapp /opt/springapp
sudo systemctl daemon-reload
sudo systemctl start springapp
sudo systemctl enable springapp
Verify the application responds:
curl -s http://localhost:8080/actuator/health
Step 3 — nginx reverse proxy configuration
Install nginx and create the site configuration:
sudo apt install -y nginx
Create /etc/nginx/sites-available/springapp:
upstream springboot {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name example.com;
location /static/ {
alias /opt/springapp/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://springboot;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
proxy_read_timeout 60s;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
}
}
Enable and test:
sudo ln -s /etc/nginx/sites-available/springapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Step 4 — SSL termination with Certbot
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com
Configure Spring Boot to trust the proxy headers so HttpServletRequest.isSecure() returns true:
# application-production.yml
server:
forward-headers-strategy: native
Step 5 — Proxy headers and Actuator health
Expose the health endpoint for monitoring but restrict management endpoints:
management:
endpoints:
web:
exposure:
include: health, info, metrics
server:
port: 8081
Running management on a separate port (8081) lets you block external access via nginx while still monitoring internally:
curl http://localhost:8081/actuator/health
With Reflex
Reflex monitors both nginx and the Spring Boot process as a unified stack. It tracks upstream error rates, JVM heap usage, GC pause times, and Actuator health status. When issues arise, Reflex can restart the service, verify health through the Actuator endpoint, and provide correlated diagnostics from both nginx and application logs. See How it works.