How I build an nginx reverse proxy config without memorising everything
· 5 min read
An nginx reverse proxy config can look intimidating when you are still learning:
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}
The config is not random. It is just a set of instructions that says:
When someone visits this domain, send the request to my app running somewhere else, then return the app’s response to the visitor.
The nginx Reverse Proxy Wizard helps build the config step by step so I do not have to remember every directive from scratch.
What a reverse proxy does
A reverse proxy sits in front of an app.
The visitor talks to nginx:
browser -> nginx
nginx talks to the app:
nginx -> app
Then nginx sends the app’s response back to the visitor.
This is useful because nginx can handle the public-facing parts:
- domain name;
- TLS/HTTPS;
- static files;
- request size limits;
- proxy headers;
- multiple apps on one server.
Your app can stay on a private local port, such as 127.0.0.1:3000.
Step 1: know your public domain
Start with the domain people will open in the browser.
Example:
app.example.com
In nginx, this becomes:
server_name app.example.com;
The domain should already point to your server’s IP address with DNS. nginx cannot fix DNS. It can only respond after traffic reaches the server.
Step 2: know where your app is running
Next, identify the app address from the server’s point of view.
Common examples:
http://127.0.0.1:3000
http://localhost:8080
http://172.18.0.5:5000
http://app:3000
If the app runs directly on the same server, 127.0.0.1:3000 is common.
If the app runs in Docker Compose and nginx is in the same Compose network, the upstream might be a service name like:
http://app:3000
The important question is:
Can nginx reach this address from where nginx is running?
Step 3: choose the basic reverse proxy template
Open the nginx Reverse Proxy Wizard. Fill in:
- the domain name;
- the upstream app URL;
- whether TLS is handled by nginx;
- any extra options you need.
A minimal generated config usually has:
location / {
proxy_pass http://127.0.0.1:3000;
}
That line means:
Send requests under
/to the app athttp://127.0.0.1:3000.
Step 4: keep the forwarding headers
A reverse proxy changes the path a request takes. Without extra headers, the app may not know the original host, IP, or protocol.
These headers are commonly used:
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;
Plain meaning:
Host: the domain the visitor requested;X-Real-IP: the direct client IP seen by nginx;X-Forwarded-For: the chain of client/proxy IPs;X-Forwarded-Proto: whether the original request usedhttporhttps.
Many apps need these to build correct redirects, logs, and absolute URLs.
Step 5: understand HTTP vs HTTPS
There are two common setups.
First setup: nginx handles HTTPS directly.
browser --HTTPS--> nginx --HTTP--> app
This is common. The app only listens locally over HTTP. nginx handles certificates.
Second setup: a load balancer or CDN handles HTTPS before nginx.
browser --HTTPS--> CDN/load balancer --HTTP or HTTPS--> nginx --HTTP--> app
In that case, make sure nginx and the app understand the forwarded protocol headers. Otherwise the app may think the request is HTTP and generate wrong redirects.
Step 6: decide whether to redirect HTTP to HTTPS
For public sites, I usually want HTTP to redirect to HTTPS.
That looks like:
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri;
}
Plain meaning:
If someone visits the HTTP version, send them to the HTTPS version.
Only enable this after HTTPS is actually working. If the certificate is not ready, you can create a redirect loop or a broken site.
Step 7: handle WebSockets if the app needs them
Some apps use WebSockets for realtime features. Examples include dashboards, chat, live reload, and some admin panels.
A WebSocket-friendly proxy usually includes:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
If an app works normally but realtime features fail, check whether WebSocket support is missing in the proxy config.
Step 8: set upload size if needed
nginx has request size limits. If your app accepts file uploads, you may need:
client_max_body_size 20m;
Plain meaning:
Allow request bodies up to 20 MB.
Set this intentionally. Do not make it huge unless the app really needs it.
Step 9: save the config in the right place
On many Ubuntu/Debian servers, a site config goes here:
/etc/nginx/sites-available/app.example.com
Then it is enabled with a symlink:
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
Some systems use:
/etc/nginx/conf.d/app.example.com.conf
Use the layout your server already uses. Do not mix styles unless you know why.
Step 10: test before reload
Always test nginx config before reloading:
sudo nginx -t
If the test passes, reload:
sudo systemctl reload nginx
If the test fails, nginx will show the file and line number. Fix that first. Do not restart blindly.
Step 11: test the public URL
After reload, test the domain:
curl -I https://app.example.com
Useful things to check:
- does it return the expected status code?
- does it redirect too many times?
- does the app generate correct URLs?
- do uploads work?
- do WebSockets work if needed?
- does the app log show the correct client IP?
Common mistakes
These are the mistakes I check first:
- DNS does not point to the server;
- app is not running;
- nginx cannot reach the upstream address;
- app listens on
127.0.0.1inside a container, not the host; - wrong
server_name; - forgot
sudo nginx -tbefore reload; - HTTPS redirect enabled before certificate is ready;
- missing WebSocket headers;
- upload size too small.
My reverse proxy checklist
When I build a reverse proxy config, I check:
- What public domain should nginx answer for?
- Does DNS point to this server?
- What upstream URL can nginx reach?
- Do I need HTTPS in nginx?
- Should HTTP redirect to HTTPS?
- Does the app need WebSocket support?
- Does the app need larger uploads?
- Are forwarding headers included?
- Does
sudo nginx -tpass? - Did I reload nginx, not just edit the file?
The wizard does the boring syntax work. The important human part is knowing the domain, the upstream app address, and which extra behaviours the app needs.
Comments
Comments are welcome — please read the comment policy first. Powered by giscus and GitHub Discussions.