Back to Article List

Fix webhook URL issues in n8n behind a reverse proxy

Fix webhook URL issues in n8n behind a reverse proxy

n8n builds every webhook URL from N8N_WEBHOOK_URL. When that variable is empty it falls back to N8N_PROTOCOL://N8N_HOST:N8N_PORT with N8N_PATH appended, which on an untouched Docker install produces http://localhost:5678/. It does not look at the Host, X-Forwarded-Host or X-Forwarded-Proto headers of the request that loaded the editor. So a Webhook node showing localhost behind a perfectly configured proxy is expected: the proxy is forwarding everything correctly and n8n was never told its public address. Everything below follows from that one rule.

How n8n builds webhook URLs

The docs phrase it as n8n "creates the webhook URL by combining N8N_PROTOCOL, N8N_HOST and N8N_PORT", and the reverse proxy webhook page tells you to set N8N_WEBHOOK_URL manually whenever a proxy sits in front, so the editor displays the right address and external services get the right address registered with them. Both test and production webhooks use the same base.

WEBHOOK_URL is the old name. It's deprecated since n8n 2.35.0, still works as an alias and logs a warning at startup. Every compose file written before mid-2026 uses it, including n8n's own Traefik example, so seeing it isn't a bug, but new configs should carry the new name and old ones can be renamed at the next restart.

The full URL for a production webhook is N8N_WEBHOOK_URL + webhook/ + the path set in the node. The three path segments are configurable: N8N_ENDPOINT_WEBHOOK (default webhook), N8N_ENDPOINT_WEBHOOK_TEST (default webhook-test) and N8N_ENDPOINT_WEBHOOK_WAIT (default webhook-waiting).

Environment variables for n8n behind a reverse proxy

This is the set I put on every instance that has a proxy in front, with n8n.example.com as the public name:

N8N_HOST=n8n.example.com
N8N_PORT=5678
N8N_PROTOCOL=https
N8N_EDITOR_BASE_URL=https://n8n.example.com
N8N_WEBHOOK_URL=https://n8n.example.com/
N8N_PROXY_HOPS=1

Strictly, N8N_WEBHOOK_URL alone fixes the webhook display. I set the other three anyway because N8N_HOST and N8N_PROTOCOL feed things like the OAuth callback URL shown in credential dialogs, and a config where the fallback would produce the same answer as the override is one fewer thing to debug at 11pm. The trailing slash on the webhook URL matches the docs example. N8N_PORT stays 5678 because that is the port n8n listens on inside the container; it is not the public port, the proxy owns that.

What N8N_PROXY_HOPS does

N8N_PROXY_HOPS is "Number of reverse-proxies n8n is running behind" in the deployment variables table (default 0) and it has nothing to do with URL construction. It sets how many entries n8n trusts in the X-Forwarded-For chain when working out the real client IP, which is what the login rate limiter and the request logs use. At 0 behind a proxy, every visitor looks like 127.0.0.1 and the rate limiter counts them as one client; you may also see a warning in the logs about a forwarded header being present while trust proxy is off. Set it to the number of proxies that add a hop: 1 for nginx or Caddy on the same box, 2 for a cloud load balancer in front of nginx.

The docs add that the last proxy on the path must send X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto. Caddy does all three by default, which is why the Ubuntu 24.04 install with Docker and Caddy has no header config at all; nginx needs explicit proxy_set_header lines, which the n8n nginx reverse proxy guide lists in full. And if the instance-level MCP server sits behind the same proxy, the MCP request headers have to be forwarded too.

N8N_EDITOR_BASE_URL and N8N_SECURE_COOKIE

N8N_EDITOR_BASE_URL is the public URL of the editor. It goes into links n8n generates for people: user invitations, password resets, the link back to a failed execution. It doesn't affect webhooks. Leave it unset and those links carry whatever the fallback produces, which behind a proxy is again localhost.

N8N_SECURE_COOKIE defaults to true, meaning the session cookie is marked Secure and browsers only send it over HTTPS. Accessing n8n over plain HTTP by IP with that default leaves you unable to log in, and n8n shows a message about the secure cookie setting. The right fix is the proxy with TLS. The bypass for a lab box is N8N_SECURE_COOKIE=false, and I'd never leave that on an instance that faces the internet.

Test and production webhook paths

A Webhook node shows two URLs. The test URL uses /webhook-test/ and only answers while you've clicked "Listen for test event" in the editor, for a single request, then goes back to 404. The production URL uses /webhook/ and answers whenever the workflow is published. Wait nodes that pause for a callback answer under /webhook-waiting/. All three share the same base, so if the production URL is right the others are too, and a provider that got the test URL pasted into its settings will report failures the moment you stop listening. I still do this about once a month with Stripe.

Path-based routing with N8N_PATH

To serve n8n under https://example.com/n8n/ on a shared domain, set N8N_PATH=/n8n/ and N8N_WEBHOOK_URL=https://example.com/n8n/, then have the proxy pass /n8n/ through without stripping the prefix. The editor's assets are served relative to that path. In practice a subdomain is less work: no prefix to preserve at the proxy, no path to remember in every provider's webhook settings, and the 2.x MCP and chat endpoints assume the root by default. I have not run N8N_PATH with a subpath in front of queue-mode webhook processors, so how that interacts with load balancer path rules is something I'd test on a staging box first.

Symptoms, causes and the variable to change

SymptomCauseFix
Webhook node shows http://localhost:5678/No public address configuredSet N8N_WEBHOOK_URL (or N8N_HOST + N8N_PROTOCOL)
URL shows http:// with the right domainN8N_HOST set, N8N_PROTOCOL still httpN8N_PROTOCOL=https or use N8N_WEBHOOK_URL
URL shows :5678 after the domainFallback includes N8N_PORTN8N_WEBHOOK_URL=https://domain/ has no port
Provider gets 404 on the webhookTest URL pasted or workflow not publishedUse the /webhook/ URL and publish
Deprecation warning about WEBHOOK_URL at startupOld variable name, n8n 2.35+Rename to N8N_WEBHOOK_URL
Can't log in over HTTP by IPSecure cookie on plain HTTPUse HTTPS (N8N_SECURE_COOKIE=false only for a lab)
Redirect loop at the proxyEdge terminates TLS and talks HTTP to an origin that redirects to HTTPSForward X-Forwarded-Proto: https, or use full TLS to the origin
All logins rate limited togetherN8N_PROXY_HOPS at 0Set it to the real hop count
Invite or reset links point to localhostN8N_EDITOR_BASE_URL unsetSet it to the public editor URL

AWS ALB, Cloudflare and other proxies in front of n8n

An Application Load Balancer that terminates TLS and forwards to an nginx or directly to the n8n container is still just a proxy from n8n's point of view. The webhook variables are identical to the single-proxy case, N8N_PROXY_HOPS becomes 2 if the ALB sits in front of nginx (1 if it talks to n8n directly), and the ALB's idle timeout has to be longer than your slowest synchronous webhook or the caller receives a 504 from the balancer while n8n finishes the run. WebSocket support is on by default on ALBs, so the editor's push connection works without extra rules.

Cloudflare in proxied (orange cloud) mode is the same story with two extra points. Its SSL mode has to be Full or Full (strict), because Flexible sends plain HTTP to your origin and a proxy that redirects HTTP to HTTPS then loops forever. And Cloudflare adds its own hop, so a Cloudflare, nginx, n8n chain is N8N_PROXY_HOPS=2, with nginx also needing to trust Cloudflare's IP ranges before the client address it forwards is the visitor's and not Cloudflare's. None of that changes N8N_WEBHOOK_URL, which stays the public hostname with https://. The rest of the proxy-layer hardening, including rate limits and signature checks on the webhook paths themselves, is in the guide on securing n8n webhooks.

Automate faster, for less

Bring your winning ideas to life with AMD power, NVMe speed and unmetered bandwidth.