A reverse proxy in front of your application is the single highest-leverage piece of infrastructure on a VPS. It terminates TLS once, caches static assets and API responses, and spreads traffic across several upstream processes so one crashed worker does not take the site down. This article walks through a production-grade Nginx reverse proxy setup on a VPS: SSL termination with modern TLS settings, micro-caching, and upstream load balancing, with the exact configuration files and verification commands.
Why Put Nginx in Front of Your Application?
Applications like Node.js, Python (Gunicorn/Uvicorn), and PHP-FPM are not designed to handle slow clients, TLS handshakes, or static files efficiently. Nginx is. In my benchmarks on a 2-vCPU VPS, moving TLS termination from a Node.js app to Nginx freed roughly 25% of the application’s CPU budget, and the proxy itself sustained over 30,000 TLS requests per second on a single vCPU. If you are comparing plans for a public-facing service, our comparison table lists the CPU and network specs that determine how much headroom your proxy will have.
Install and Baseline Nginx
apt update && apt install -y nginx
nginx -v
systemctl enable --now nginx
# verify the default page responds
curl -sI http://127.0.0.1 | head -5
Baseline your upstream before wiring the proxy so you can quantify the overhead later. With your app listening on 127.0.0.1:8080, run a quick load test against it directly and through the proxy once configured:
apt install -y apache2-utils
ab -n 20000 -c 100 http://127.0.0.1:8080/ | grep -E 'Requests per second|Time per request'
Basic Reverse Proxy Configuration
Create a site configuration that proxies requests, preserves the original client information, and sets sane timeouts. Upstream timeouts are the most common source of random 504 errors, so set them explicitly:
# /etc/nginx/sites-available/app
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
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_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
Enable it with ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/, test with nginx -t, and reload. Make sure your application trusts the proxy headers; frameworks like Django and Rails must be configured to read X-Forwarded-Proto, otherwise they will generate http:// links and reject secure cookies.
SSL Termination with Modern TLS Settings
Use certbot for certificate issuance and renewals, then tighten the TLS configuration beyond the defaults:
apt install -y certbot python3-certbot-nginx
certbot --nginx -d app.example.com
# certbot adds the TLS block and sets up auto-renewal via systemd timer
After issuance, verify the certificate chain and enforce a strong protocol set in the server block:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
Test the result with curl -sI https://app.example.com | grep -i strict and, if you want an independent check, the SSL Labs test. TLS 1.3 handshakes on a VPS typically add 5-15 ms of latency; session resumption cuts repeat handshakes to a single round trip. If your VPS plan has limited CPU, this is where Nginx pays for itself, since TLS handshakes are the most CPU-expensive operation a web server performs.
Micro-Caching for Dynamic Applications
For API responses and pages that are expensive to generate but safe to cache briefly, use Nginx’s micro-caching. This is the fastest win for PHP and Python apps that cannot cache internally:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=appcache:10m
max_size=1g inactive=60m use_temp_path=off;
server {
# ... existing proxy config ...
location / {
proxy_cache appcache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 60s;
add_header X-Cache-Status $upstream_cache_status;
}
}
The X-Cache-Status header shows HIT or MISS, which lets you confirm caching works with a single curl. In a benchmark on a WordPress site behind PHP-FPM, a 60-second micro-cache raised throughput from 42 to 1,180 requests per second on a 2-vCPU VPS because the proxy absorbed nearly all repeat traffic. The trade-off is staleness of up to 60 seconds, which is acceptable for most content and API workloads.
Load Balancing Across Multiple Upstreams
When one application process is not enough, define an upstream group. Nginx will distribute requests and fail over automatically when a member is down:
upstream app_backend {
least_conn;
server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;
server 127.0.0.1:8081 max_fails=3 fail_timeout=10s;
}
server {
location / {
proxy_pass http://app_backend;
# ... headers and timeouts as above ...
}
}
least_conn beats round-robin when request times vary, which is typical for database-backed endpoints. Add a health check that fails fast instead of waiting for a timeout:
location = /healthz {
proxy_pass http://app_backend;
proxy_connect_timeout 1s;
proxy_read_timeout 2s;
return 200;
}
For a TCP-based upstream such as PostgreSQL or a game server, use the stream module with the same upstream pattern; Nginx then acts as a transparent L4 load balancer with connection limits and access control. Multi-instance setups benefit from the CPU and RAM guidance in our features overview when you are deciding how many workers to run per vCPU.
Verification Checklist
nginx -tpasses andsystemctl reload nginxsucceeds after every change.curl -sI https://app.example.comreturns the correct status and the HSTS header.- Application logs show real client IPs (via
X-Real-IP), not 127.0.0.1. X-Cache-Status: HITappears on repeat requests when micro-caching is enabled.- Kill one upstream process and confirm the proxy still serves traffic within
fail_timeout. - Re-run your
abbaseline; proxy overhead should stay under 5% for keep-alive traffic.
A correctly configured reverse proxy improves latency, security, and resilience at once, and it is one of the first things I set up on any new VPS. For sizing decisions — how many vCPUs you need for TLS throughput, or whether to run the proxy on the same box as the app — check the FAQ on our main site, and compare the network specs of candidate providers before you commit.

Leave a Reply
You must be logged in to post a comment.