What a Reverse Proxy Does
A reverse proxy sits in front of one or more backend applications and forwards client requests to them. Instead of exposing your app server (Node.js, Python, PHP-FPM, a Docker container) directly to the internet, clients talk to Nginx, and Nginx talks to your app over a private address.
This gives you a single public entry point where you can terminate TLS, balance traffic across multiple backends, cache responses, enforce rate limits, and hide internal topology. It is the standard pattern for production web apps — and it pairs naturally with a VPS or cloud server from Skyline Cloud, where your data stays in-Kingdom for PDPL and NCA alignment.
This guide uses Ubuntu/Debian paths, but every directive applies identically on RHEL-family systems.
Prerequisites
- A Linux server with
sudoaccess (an Skyline Cloud VPS or cloud server works well). - A backend app already running and listening locally — for example on
127.0.0.1:3000. - A domain (such as a
.sadomain) with anArecord pointing to your server's public IP.
Step 1 — Install Nginx
sudo apt update
sudo apt install nginx -y
Confirm it is running and enabled at boot:
sudo systemctl status nginx
sudo systemctl enable nginx
Open the firewall if you use UFW:
sudo ufw allow 'Nginx Full'
Step 2 — Create the Reverse Proxy Server Block
Nginx loads any file ending in .conf from /etc/nginx/conf.d/, and on Debian/Ubuntu it also reads /etc/nginx/sites-enabled/. Create a dedicated file so you do not touch the default:
sudo nano /etc/nginx/conf.d/app.conf
Add the following, replacing app.example.com and the backend address with your own:
server {
listen 80;
listen [::]:80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
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 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
Why each header matters
| Directive | Purpose |
|---|---|
proxy_pass |
The backend address Nginx forwards to. No trailing slash here keeps the original URI path. |
Host |
Passes the original hostname so the app generates correct links and routes by domain. |
X-Real-IP |
The client's real IP, which the proxy would otherwise hide. |
X-Forwarded-For |
Appends each proxy hop so the app sees the full client chain. |
X-Forwarded-Proto |
Tells the app whether the original request was http or https — essential once you add TLS. |
Step 3 — Test and Reload
Always validate the configuration before applying it. This catches syntax errors before they take your site down:
sudo nginx -t
You should see syntax is ok and test is successful. Then reload — reload re-reads config gracefully with near-zero interruption, unlike restart:
sudo systemctl reload nginx
Visit http://app.example.com and you should see your backend app served through Nginx.
Step 4 — Support WebSockets
If your app uses WebSockets (chat, live dashboards, dev servers), you must forward the Upgrade and Connection headers, or the connection will fail to switch protocols. Add these inside the same location block:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
The proxy_http_version 1.1; line from Step 2 is required for this to work.
Step 5 — Add HTTPS with a Free Certificate
Terminate TLS at Nginx so the backend only handles plain HTTP internally. Install Certbot and let it configure SSL automatically:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d app.example.com
Certbot edits your server block, adds a listen 443 ssl server, and sets up an automatic renewal timer. Verify renewal works with a dry run:
sudo certbot renew --dry-run
To enable HTTP/2, change the listen line in the SSL block to the cross-version form:
listen 443 ssl http2;
Use
listen 443 ssl http2;rather than the separatehttp2 on;directive — the combined form works on all current Nginx versions.
Step 6 — Load Balance Across Multiple Backends
To distribute traffic across several app instances, define an upstream group and proxy to it by name. Place the upstream block at the top of the file, outside server:
upstream backend_pool {
least_conn;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002 backup;
keepalive 32;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend_pool;
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_set_header Connection "";
}
}
least_connsends each request to the backend with the fewest active connections. The default (no directive) is round-robin.backupmarks a server used only when the others are down.keepalive 32reuses upstream connections. When using it, setproxy_set_header Connection "";to avoid closing them per request.
Step 7 — Cache Backend Responses (Optional)
Caching reduces load on your app for content that does not change per user. Define the cache store in the http context (for example in /etc/nginx/nginx.conf):
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:10m
max_size=1g inactive=60m use_temp_path=off;
Then reference it inside the location block:
proxy_cache app_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
add_header X-Cache-Status $upstream_cache_status;
The X-Cache-Status response header reports HIT, MISS, or BYPASS, so you can confirm caching from your browser's network tab.
Troubleshooting
- 502 Bad Gateway — the backend is not reachable. Confirm it is listening with
ss -tlnp | grep 3000and that the address inproxy_passmatches. - 504 Gateway Timeout — a slow backend; raise
proxy_read_timeout. - App generates
http://links behind HTTPS — make sureX-Forwarded-Proto $schemeis set and your app trusts it. - Check the logs:
sudo tail -f /var/log/nginx/error.log.
Wrapping Up
You now have a production-grade reverse proxy: TLS termination, correct client headers, WebSocket support, load balancing, and optional caching. Run it close to your users and your data — Skyline Cloud hosts everything in-Kingdom with PDPL/NCA alignment, local Arabic support, and transparent pricing. Pair this proxy with our managed business email hosting for a complete stack, and explore more guides in our Saudi web hosting hub.
Ready to deploy? Create your Skyline Cloud account and spin up a VPS in minutes.
Comments
0 total · 0 threads