Nginx Reverse Proxy Configuration#
Dify ships a fully template-driven Nginx setup under docker/nginx/ that fronts all services in the default Docker Compose deployment. It is not intended to be edited directly — all knobs are exposed as environment variables consumed at container startup.
File Map#
| File | Purpose |
|---|---|
nginx.conf.template | Worker processes, keepalive, client_max_body_size, global HTTP settings |
proxy.conf.template | Shared proxy headers and timeouts; included by every location block |
conf.d/default.conf.template | Server block, route→upstream mappings, optional HTTPS/ACME placeholders |
docker-entrypoint.sh | Runs envsubst over all templates at startup to produce final .conf files |
envs/infrastructure/nginx.env.example | Canonical list of all supported env vars with defaults |
Key Proxy Settings (proxy.conf.template)#
All location blocks include proxy.conf , which enforces three important settings:
proxy_buffering off— disables Nginx's response buffer. This is critical for Server-Sent Events (SSE) streams used by the workflow debug console and chat streaming endpoints; buffering would hold data until the buffer fills, breaking real-time delivery.proxy_http_version 1.1+Connection ""— enables HTTP/1.1 keepalive to upstream, required for efficient connection reuse and correct chunked-transfer-encoding handling.X-Forwarded-*headers —Host,X-Forwarded-For,X-Forwarded-Proto, andX-Forwarded-Portare all forwarded so backend services can reconstruct the original client request context.
Timeouts are long by design — both proxy_read_timeout and proxy_send_timeout default to 3600s to accommodate long-running LLM completions and workflow executions.
Routing (default.conf.template)#
The server block maps URL prefixes to three upstream services :
| Location | Upstream | Notes |
|---|---|---|
/console/api, /api, /v1, /openapi, /files, /mcp, /triggers | api:5001 | Core API service |
/socket.io/ | ${NGINX_SOCKET_IO_UPSTREAM} (default api_websocket:5001) | WebSocket; overrides Connection header to upgrade |
/explore, / | web:3000 | Next.js frontend |
/e/ | plugin_daemon:5002 | Plugin execution endpoints; adds Dify-Hook-Url header |
All upstreams use dynamic DNS resolution: a server-level resolver (127.0.0.11, the Docker embedded DNS, with 30s validity) and $variable-based proxy_pass directives prevent Nginx from caching incorrect upstream IPs at startup — a problem that can occur when the host's DNS search domains resolve short service names (api, web) to external addresses instead of Docker containers after restarts.
Environment Variables#
Configured via .env (or docker/envs/infrastructure/nginx.env.example as reference) :
| Variable | Default | Effect |
|---|---|---|
NGINX_WORKER_PROCESSES | auto | Workers spawned |
NGINX_CLIENT_MAX_BODY_SIZE | 100M | Max upload size — raise for large plugin packages |
NGINX_KEEPALIVE_TIMEOUT | 65 | TCP keepalive in seconds |
NGINX_PROXY_READ_TIMEOUT | 3600s | Upstream read timeout |
NGINX_PROXY_SEND_TIMEOUT | 3600s | Upstream send timeout |
NGINX_PORT | 80 | HTTP listen port |
NGINX_HTTPS_ENABLED | false | Enables TLS block from https.conf.template |
NGINX_ENABLE_CERTBOT_CHALLENGE | false | Adds ACME /.well-known/acme-challenge/ location |
NGINX_SOCKET_IO_UPSTREAM | api_websocket:5001 | WebSocket upstream target |
Startup Process#
docker-entrypoint.sh runs envsubst over all templates at container start . If NGINX_HTTPS_ENABLED=true, it additionally resolves the correct certificate path — checking /etc/letsencrypt/live/<domain>/ first (Certbot), falling back to /etc/ssl/ — and injects the rendered https.conf.template snippet into the server block .
Operational Notes#
- Do not edit
.conffiles directly — they are overwritten on every container restart byenvsubst. Edit the corresponding.envvariable instead . - Plugin uploads and large bodies — the default
client_max_body_size 100Mgates plugin package uploads. If the Nginx log shows aclient request body is buffered to a temporary filewarning, the request body exceeded nginx's in-memory buffer but will still complete; this is not a failure. - External reverse proxy in front of Dify's Nginx — if you add another proxy layer (e.g., Cloudflare, another Nginx), ensure it also sets
proxy_buffering off(or equivalent) and long timeouts for the/console/apiand/v1paths; buffering at that layer will break SSE streaming even if Dify's internal Nginx is configured correctly. TheHostheader should use$http_host(not$host) if non-standard ports are in use. - Chunked transfer encoding — because
proxy_buffering offis set globally, Nginx passes chunked responses from the API directly to the client without accumulating them, which is the correct behavior for streaming endpoints. If buffering were enabled without an explicitContent-Length, Nginx would fail to set the header and could prematurely close connections.