Nginx Reverse Proxy Setup for n8n

Ahmed
0

Nginx Reverse Proxy Setup for n8n

I’ve deployed n8n behind Nginx on small U.S.-hosted VPSs where one missed header quietly broke webhooks and made the editor feel “randomly unstable.”


Nginx Reverse Proxy Setup for n8n is the clean way to run a secure HTTPS endpoint, keep webhook URLs correct, and stop WebSocket disconnects that ruin real automations.


Nginx Reverse Proxy Setup for n8n

What a “correct” reverse proxy looks like for n8n in production

You want three outcomes—no compromises:

  • HTTPS everywhere so credentials and webhook payloads are encrypted in transit.
  • Accurate external URLs so n8n registers the right webhook endpoints with services like Stripe, Slack, Google, and CRMs.
  • Stable WebSockets so the editor stays connected (no “connection lost” surprise mid-build).

The most reliable pattern is: Public Internet → Nginx (TLS termination) → n8n (localhost or private Docker network). The key is making Nginx forward the original request details and letting n8n know the public base URL.


Prerequisites you should lock in before touching Nginx

  • A domain or subdomain pointing to your server public IP (common U.S. setups: AWS Lightsail, DigitalOcean NYC/SFO, Vultr, Linode).
  • Port 80 and 443 open in your firewall/security group.
  • n8n reachable locally (for example, http://127.0.0.1:5678) via Docker or a system service.

Set the n8n variables that make reverse proxying predictable

Most “it loads but webhooks fail” cases come from n8n generating internal URLs (localhost) instead of public URLs. Fix that by explicitly setting the webhook URL and telling n8n how many proxies sit in front of it. n8n documents this approach for reverse proxy setups and the forwarded headers you must pass through. n8n webhook URL configuration (official)


Use these values as your baseline (adjust the domain):

# n8n reverse proxy essentials

WEBHOOK_URL=https://n8n.example.com/ N8N_PROXY_HOPS=1 # Recommended when you want the editor to always render the public URL
N8N_EDITOR_BASE_URL=https://n8n.example.com/

Real-world challenge (n8n): after you “fix” HTTPS in Nginx, OAuth callbacks and production webhooks still point to http or localhost. Fix: set WEBHOOK_URL (and N8N_EDITOR_BASE_URL when needed) to the exact public HTTPS URL and restart n8n.


Build an Nginx config that supports WebSockets and clean forwarding

n8n depends on WebSockets for a stable editor experience, so you must forward Upgrade/Connection correctly. Nginx documents the WebSocket proxy mechanism and the Upgrade flow. Nginx WebSocket proxying (official)


This server block is a solid starting point for a dedicated subdomain like n8n.example.com:

# /etc/nginx/sites-available/n8n.conf

map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 80; server_name n8n.example.com; # Keep this simple if you're using Certbot, it will manage redirects. location / { proxy_pass http://127.0.0.1:5678; # Forward the original request details proxy_set_header Host $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support (editor stability) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; # Reduce random upstream issues on larger payloads client_max_body_size 25m; # Prevent long-running connections from dying too early proxy_read_timeout 3600s; proxy_send_timeout 3600s; }
}

Real-world challenge (Nginx): everything “works,” but the editor drops with a WebSocket error or shows “connection lost” during active use. Fix: ensure proxy_http_version 1.1 is set and both Upgrade + Connection headers are forwarded, then reload Nginx.


Enable HTTPS with Let’s Encrypt in a way that won’t surprise you later

The common path on Ubuntu is Certbot with the Nginx plugin so renewals stay automated. Use the official Certbot instructions for your OS and web server. Certbot (official instructions)


After you issue a certificate and your Nginx config is updated, verify:

  • Your browser shows HTTPS with no certificate warnings.
  • HTTP redirects cleanly to HTTPS (one hop, not loops).
  • n8n’s editor shows webhook URLs as https://… (not http:// or localhost).

Real-world challenge (Certbot / Let’s Encrypt): renewals fail months later because port 80 is blocked or a different service started listening on it. Fix: keep port 80 open (even if you redirect), and periodically run a dry-run renewal test on your server.


Docker vs bare-metal: which one makes reverse proxying easier?

You can reverse proxy either way, but Docker usually makes network boundaries cleaner and reduces accidental exposure of port 5678 to the public internet.


Deployment choice What goes right Where it breaks Practical fix
Docker (recommended for most) Private network isolation, simpler upgrades, consistent restarts Confusing port bindings or mixing host networking with bridge networks Bind n8n to localhost only (127.0.0.1:5678) or a private Docker network, and let Nginx be the only public entry
System service (npm / binary) Fewer moving parts, direct file paths, easy log access Accidentally exposing port 5678 publicly or forgetting environment variables on restart Bind to localhost, put env vars in a service file, and reload the service after changes

Real-world challenge (Docker): you publish n8n on 0.0.0.0:5678, then later add Nginx—now you have two public entry points and inconsistent behavior across nodes/services. Fix: remove the public port mapping (or bind it to 127.0.0.1 only) and force all traffic through Nginx.


If you need Docker references for networking and port binding behavior, use the official docs. Docker documentation (official)


Hardening that matters for U.S.-facing deployments

  • Limit direct access: keep n8n on localhost/private network only; Nginx is your only public edge.
  • Protect credentials: store n8n secrets in environment variables (or a secrets file mechanism), not inside random scripts.
  • Reduce brute-force noise: if you see constant login attempts, add rate limiting at the Nginx layer and consider IP allowlists for admin access.
  • Keep logs useful: enable access/error logging for the n8n vhost so you can trace webhook failures quickly.

Common mistakes that cause “webhooks don’t fire” even though the UI works

  • WEBHOOK_URL missing or wrong: external services register the wrong callback URL.
  • Proxy headers not forwarded: n8n can’t reconstruct the original HTTPS request reliably.
  • HTTPS not terminating at the last proxy: you terminate TLS somewhere else but forget to forward X-Forwarded-Proto correctly.
  • WebSockets not supported: editor becomes unstable and you chase the wrong “n8n bug.”
  • Multiple entry points: direct :5678 access works, but the public domain behaves differently.

Troubleshooting checklist you can run in 5 minutes

  • Open https://n8n.example.com and confirm you’re not being redirected in loops.
  • In your browser dev tools, confirm WebSocket connections are not failing repeatedly.
  • Check Nginx error logs for 502/504 responses during webhook calls.
  • Confirm your n8n environment variables are applied (restart the service/container after edits).
  • Confirm port 5678 is not publicly exposed (scan your server or check firewall rules).

FAQ

Why does n8n show localhost in webhook URLs when I’m using a domain?

This usually means n8n is building URLs from its local binding instead of your public domain. Set WEBHOOK_URL to your exact HTTPS domain (including the trailing slash) and restart n8n so it consistently registers the public endpoint.


Do I need N8N_EDITOR_BASE_URL if WEBHOOK_URL is already set?

If the editor UI still renders links, OAuth redirects, or callback URLs inconsistently, N8N_EDITOR_BASE_URL forces the UI to use the public base URL. When everything is on a single public domain, setting both keeps behavior predictable across updates and integrations.


What headers are non-negotiable when Nginx sits in front of n8n?

At minimum, forward Host, X-Forwarded-For, and X-Forwarded-Proto so n8n can reconstruct the original request. Without these, you’ll see mismatched origins, wrong callback URLs, or HTTPS confusion when services hit your webhooks.


How do I stop “connection lost” in the n8n editor behind Nginx?

That symptom strongly points to WebSocket handling. Make sure proxy_http_version 1.1 is set and Upgrade + Connection headers are forwarded in the same location block that proxies to n8n, then reload Nginx.


Can I host n8n under a subpath like example.com/n8n instead of a subdomain?

You can, but it’s a higher-friction setup because cookies, base paths, and asset routing become easier to break during upgrades. A dedicated subdomain (n8n.example.com) is the most stable pattern when you care about long-term reliability.


What’s the safest way to ensure n8n isn’t exposed directly to the public internet?

Bind n8n to localhost (127.0.0.1:5678) or a private Docker network, don’t publish 5678 publicly, and let Nginx be the only service listening on 80/443.


Why do some integrations succeed in testing but fail in production mode?

Testing can run through the UI session while production webhooks rely on the public callback URL registered with the third-party service. If your public URL, HTTPS headers, or webhook base URL are off by even a little, production events won’t reach your workflow.



Wrap-up

Once Nginx is forwarding the right headers, WebSockets are upgraded correctly, and n8n is told its real public HTTPS URL, your instance behaves like a professional U.S.-market automation endpoint instead of a “works on my server” experiment. Lock the variables, keep TLS renewals healthy, and you’ll stop losing hours to invisible reverse-proxy edge cases.


Post a Comment

0 Comments

Post a Comment (0)