Skip to main content

Spring Boot + nginx reverse proxy — Linux setup

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.