Proxying Spring Boot and Node.js

Introduction

Most Hello Code backends are Spring Boot JARs on port 8080 or Node apps on port 3000. This chapter gives copy-ready Nginx configs for each stack, notes health checks, and links to deployment chapters so you can go from java -jar or node dist/index.js to a public URL behind Nginx.

Prerequisites

Spring Boot on 8080

Run the JAR bound to localhost

bash
java -jar app.jar --server.address=127.0.0.1 --server.port=8080

Or in application.properties:

properties
server.address=127.0.0.1
server.port=8080
server.forward-headers-strategy=framework

Code explanation:

  • server.address=127.0.0.1 — not reachable from the internet except via Nginx
  • forward-headers-strategy — honor X-Forwarded-* for redirects and links

Nginx server block

nginx
server {
    listen 80;
    server_name api.example.com;
 
    location / {
        proxy_pass http://127.0.0.1:8080;
        include /etc/nginx/proxy_params;
    }
}

Test:

bash
curl http://api.example.com/actuator/health
curl http://api.example.com/api/your-endpoint

Adjust paths to your controllers. See the Spring Boot track for application setup.

If your team deploys a Spring Boot WAR to shared Tomcat instead of java -jar, see the Tomcat track and Nginx + Tomcat project—same proxy_pass idea, but mind WAR context paths (e.g. /api/hello not /hello).

Actuator health (optional)

If Spring Boot Actuator is enabled:

nginx
location /actuator/health {
    proxy_pass http://127.0.0.1:8080/actuator/health;
    include /etc/nginx/proxy_params;
    access_log off;
}

Use for load balancer or monitoring probes—restrict public access in production (management.endpoints.web.exposure).

Context path /api

When server.servlet.context-path=/api:

nginx
location / {
    proxy_pass http://127.0.0.1:8080;
    include /etc/nginx/proxy_params;
}

Public URL: https://api.example.com/api/... matches Spring’s context path without Nginx stripping.

Node.js on 3000

Express example

javascript
const express = require("express");
const app = express();
 
app.get("/health", (_req, res) => {
  res.json({ ok: true });
});
 
app.listen(3000, "127.0.0.1", () => {
  console.log("Listening on 127.0.0.1:3000");
});

Code explanation:

  • Listen on 127.0.0.1 only—same security pattern as Java

Nginx config

nginx
server {
    listen 80;
    server_name app.example.com;
 
    location / {
        proxy_pass http://127.0.0.1:3000;
        include /etc/nginx/proxy_params;
    }
}

See Deploying Node applications for PM2, env vars, and production process management.

Trust proxy in Express

javascript
app.set("trust proxy", 1);

Code explanation:

  • Enables correct req.ip and secure cookies behind Nginx
  • Value 1 means one proxy hop—adjust for your topology

Combined: SPA + Spring Boot API

Code explanation:

  • Static Vite/React build at /
  • API under /api/ forwarded to Spring on 8080 with prefix strip

Verify:

bash
curl -I http://shop.example.com/
curl http://shop.example.com/api/products

systemd + Nginx Workflow

Typical VPS flow:

  1. systemd unit starts JAR or Node on boot (127.0.0.1:8080)
  2. Nginx starts on 80/443
  3. Deploy: build app → restart app unit → optional static rsyncnginx -t && reload

Quick check both layers:

bash
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/actuator/health
curl -s -o /dev/null -w '%{http_code}\n' -H "Host: api.example.com" http://127.0.0.1/

Both should return 200 (or your app’s expected code).

WebSocket Preview

Node Socket.IO or Spring STOMP may need:

nginx
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Full WebSocket chapter covers this in depth.

FAQ

Spring Boot on 8080 but Nginx 502?

JAR not running, wrong port, or bound only to wrong interface—ss -tlnp | grep 8080.

Node with PM2 on 3000?

Same Nginx config—PM2 keeps Node alive; Nginx still proxies to 127.0.0.1:3000.

Separate subdomains vs /api path?

api.example.com → 8080 and www.example.com → static is cleanest CORS-wise. Single domain /api/ is simpler DNS.

gzip on JSON API?

Often enabled in http block—see gzip chapter; small JSON benefit is modest.

HTTPS before or after this chapter?

Get HTTP proxy working first; next HTTPS chapter adds listen 443 ssl.

Linux chapter 15 is a shorter path; this track adds headers, paths, and split SPA/API patterns.