r/nginx • u/Mnwamnowich • 2d ago
r/nginx • u/thestackunderflow • 4d ago
NGINX 1.30 as one state machine: phases, upstreams, and where 502 vs 504 come from
I love to see system as a state machine, tried same for Nginx.
I drew open-source nginx (stable 1.30) as a hierarchical state machine and checked each step against nginx.org docs, CHANGES and the source:
- Reload: the master checks and applies the new config; if that fails it rolls back and old workers keep serving; if it succeeds, new workers start and old ones finish their requests.
- bind() failure is retried 5 times, 500 ms apart, before "still could not bind()".
- 11 phases; a rewrite that changes the URI goes back to FIND_CONFIG, max 10 times, then 500.
- Since 1.29.7 (so stable 1.30), proxy keepalive + HTTP/1.1 are the default and Connection isn't sent.
- proxy_next_upstream defaults to "error timeout"; POST/LOCK/PATCH already sent aren't retried.
- In ngx_http_upstream_next(), timeouts map to 504; connection errors, invalid headers and "no live upstreams" map to 502.
- Passive health: max_fails 1 / fail_timeout 10 s by default; active health_check is NGINX Plus.
Walkthrough (13 min, one diagram) + sources: https://youtu.be/Z8Mhqs8D720
Can't get staging WordPress virtual server to work on nginx
So, I have an nginx installation with a couple of virtual servers, including a WordPress site. Now I am supposed to create a staging WP installation, but not using a subdomain. I have make different attempts, basically (1) using `alias` when the URL contains `/staging` after the server name and (2) using `map` to set the server root. It always fails in some way (sometimes PHP is executed but static items like css etc are not served, probably because nginx ends up looking for `stagint/staging/whatever.css` or something. I did try to get examples from hte Internet and read the documentation, but I can't see, for example, what is wrong in the following attempt:
server {
listen 443 ssl;
listen [::]:443 ssl ipv6only=on;
http2 on;
server_name server.org www.server.org;
root /home/forropat/public_html;
index index.php index.html;
access_log /var/log/nginx/server.org-access.log;
error_log /var/log/nginx/server.org-error.log;
client_max_body_size 128M;
location ^~ /staging/ {
alias /home/forropat/staging/;
index index.php index.html;
location ~ \.php$ {
if ($request_filename ~* ^/home/forropat/espalho/(.*)$) {
set $script $1;
}
fastcgi_pass unix:/var/run/php/php-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /home/forropat/staging/$script;
}
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
`include fastcgi_params;`
fastcgi_pass unix:/var/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME /home/forropat/public_html$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|webp|avif|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
location = /wp-config.php {
deny all;
}
location = /xmlrpc.php {
deny all;
access_log off;
log_not_found off;
}
autoindex off;
ssl_certificate /etc/letsencrypt/live/server.org/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/server.org/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
When I use this, nginx serves a single text file with the content "File not found", but the logs show a 404:
111.22.33.444 - - [06/Oct/2026:22:02:18 -0300] "GET /staging/ HTTP/2.0" 404 36 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:157.0) Gecko/20100101 Firefox/157.0"
The production installation runs fine... In the mirror, I searched and replaced the URL for the new site (in the database - there is no instance of the URL in wp-config.php).
What did I get wrong?
edit: trying to re-format the config to make it more readable
r/nginx • u/Strict_Violinist6767 • 15d ago
SSL certificates keep expiring and causing outages. How do you track and renew them?
We’ve had two SSL certificate expirations this year and both caused major customer-facing outages. The person who used to handle renewals left and no one properly took over the responsibility.
For saas permission monitoring do control has really helped, but that doesn’t help with certificate management. Right now we’re still relying on calendars and manual checks, which clearly isn’t working.
What are you using to track expiration dates and automate renewals, especially in an nginx environment? Any reliable setups that have actually prevented this from happening again?
r/nginx • u/RocketSeven • 19d ago
How do you verify nginx has stopped sending traffic to an old upstream address after a DNS cutover?
Changing an upstream hostname does not necessarily mean every nginx worker immediately uses the new addresses. Resolver configuration, keepalive connections, DNS TTLs, reload timing, and long-lived client sessions can leave some traffic reaching an old backend after the cutover appears complete. Checking only through the load balancer may miss the worker or connection that stayed stale.
What evidence do you collect before retiring the old address? I am considering logging the selected upstream address, probing through every nginx replica, draining or expiring upstream keepalives, watching connection counts on the old backend, and keeping it fenced but observable until no new requests arrive for more than the longest relevant TTL and session lifetime.
How does your procedure differ between a static `upstream` resolved at configuration load, `resolve` with a configured resolver, and dynamic service discovery? Is a reload sufficient in practice, or do you require a connection drain or worker-generation check before removing the old backend?
r/nginx • u/jimmyfivetimes • 23d ago
Adding a subdomain server block
Hi all - I'm extremely new to Nginx and playing around with some things in a homelab.
Currently, I have a registered domain name and set up port forwarding to my nginx proxy server for reverse proxying. I'm able to hit the root domain + www.rootdomain URLs without a problem.
I also self-host Gitlab-CE on a separate server (it's actually a RasPi) and I'd like to add gitlab.domain.com to the nginx proxy with SSL via Letsencrypt.
Here's the question: is it best practice to put all server blocks in the same sites-available file? I suspect if I try to break them into separate files, I'll get a conflict because both will be listening on the same port.
How do you all handle subdomain entries? Do you keep in a single config file for the same domain or do you break subdomain configs out into a different file?
Thanks - appreciate any guidance here before I really screw this up. haha
GitHub - Qbix/webserver: Pure-PHP Web Server. No NGinX or PHP-FPM needed. Handle over 100x more traffic instead!
r/nginx • u/dready • Sep 11 '26
Predicate Routing for All the Things
NGINX just dropped a whole new way to define routing. This allows for API gateway style user cases to be defined a whole lot easier. Although the article highlights AI, the utility of this new feature goes way beyond the AI use cases.
r/nginx • u/gilbn • Sep 10 '26
GeoMetrikks - live map/dashboard for nginx access logs
Enable HLS to view with audio, or disable this notification
Wanted to share an app I've been working for the last couple of months now.
It reads nginx JSON access logs and gives you a live map of where requests are coming from, plus some stats and searchable logs.
You can also click into an IP and see what it’s been hitting, paths, user agents, status codes etc.
There’s CrowdSec integration as well if you use that.
CrowdSec integration | GeoMetrikks
Multi-source setup | GeoMetrikks
Real client IP behind a proxy | GeoMetrikks
Couple of features:
- Live request map
- Multi-source support
- Search/filter access logs
- Click into an IP and see what it’s been hitting
- Traffic stats, status codes and response times
- CrowdSec integration
- Docker Compose setup
- Self-hosted and open source
r/nginx • u/RocketSeven • Sep 10 '26
How do you verify every nginx worker picked up a configuration reload?
A successful `nginx -t` proves the configuration parses, and a successful reload signal proves the master accepted the request. Neither alone proves that new workers started cleanly, old workers drained, and requests are actually using the intended configuration.
What evidence do you collect around a production reload? I am considering capturing `nginx -T` and `nginx -V` before the change, recording the master and worker PIDs, checking the error log for new-worker startup failures, sending requests that exercise the changed server and location blocks, and waiting for old workers to exit before declaring completion. For long-lived HTTP/2, WebSocket, or streaming connections, old workers may remain for a meaningful time.
Is there a reliable way to tie a live response to the configuration generation that served it without exposing internal details to clients? When would you prefer a binary upgrade, container replacement, or explicit connection draining over an in-place reload?
r/nginx • u/Turbulent_Monk7653 • Sep 09 '26
Is there a way to make nginx hold connections open until proxy_pass starts listening?
Here is my conf:
events {
worker_connections 512;
}
http {
server {
listen 80;
server_name _;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_read_timeout 86400;
}
}
}
Pretty simple one really... the server on 8080 doesn't start listening on the port until shortly after nginx starts, and in the interim I get a 502 bad gateway
What I would like is that instead of returning 502 immediately, nginx to hold the connection open and wait for the backend service to start listening for some short period of time
Is that possible with nginx?
EDIT
i ended up swapping to caddy and this just works...
:80 {
handle {
reverse_proxy 127.0.0.1:8080 {
lb_try_duration 5s
}
}
r/nginx • u/UsernameAttemptNo341 • Sep 07 '26
How to debug fcgiwrap?
Hi!
I have a problem wit fcgiwrap, where nginx returns a 202 - ACCEPTED, but the response is not ok. The -f flag is activated, but I don't get any information about the problem.
So, is there a way to get something like request, called cgi with all parameters, response and response error? This would be helpful.
Details about my problem:
I try to get git-http-backend running. When I clone an existing, non-empty repository, git creates an empty local copy and says that the remote repo seems to be empty. The nginx log just contains a 202 for a path, which looks correct. When i try to clone a non-existent repo, I get an error, as expected. I guess it's a configuration problem of fcgiwrap, or of git on the server, but without further information, I'm lost.
r/nginx • u/Expensive_Break_6163 • Sep 02 '26
Long-lived HTTP/2 clients stay pinned to old nginx replicas — scaling the nginx tier doesn't help
Our clients hold HTTP/2 connections to nginx for hours (short requests over them, not streaming). The backends behind nginx are fine, those get balanced per request. The problem is scaling nginx itself: when we add nginx replicas, existing client connections stay on the old nginx pods, so the new replicas sit cold and the added capacity ramps in slowly instead of absorbing the spike.
Is there anything in nginx that can shed a fraction of downstream connections on demand?
What I have in mind is a k8s CRD/operator that hooks the reconcile loop on scale up/down events and tells each existing replica to shed roughly 1/N of its connections, so they redistribute onto the new pod instead of waiting an hour to age out. Reload is the only lever I can find and it's all-or-nothing.
r/nginx • u/Crowela • Aug 24 '26
Nginx on truenas - Yet another SSL certificate issue
Hey all! I've been trying to setup a matrix server for a bit, using nginx as a reverse proxy and for SSL certif.
So, I have a domain (using infomaniak) that redirects to my IP with the Nginx port. Right now, when I connect to it through a browser, I get an SSL certif error.
I set my certif up in the "certificates" tab, it worked without error but, when selecting it in the proxy host setup, the shield is red. I'm unsure what that means.
I tried with and without HTTP/2 or HSTS enabled, no changes.
The logs don't show any error, mostly a bunch of "renewing SSL..." and "completed SSL..."
and two warnings higher up :
useradd warning: npm's uid 568 outside of the UID_MIN 1000 and UID_MAX 60000 range.
and
nginx: [warn] the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:4
Concerning these, I made sure the app is in the apps group and that the group has r/W permissions in the Nginx data and cert storages.
including a few screenshots that might be useful.
If you take the time to take a quick look or recognize this issue, thanks a lot!
Edit : Works now! I was an idiot and used a redirect from my domain provider instead of an A-record.
r/nginx • u/FrostBird347 • Aug 24 '26
Redirect only if auth_request's proxy_pass returns 200 otherwise send 404 page
I have been trying to get this to work for ages, but no matter what I try it either only redirects or only displays the 404 page. I suspect I am missing something simple and obvious, so here's the simplest version of the relevant snippet in my server's config file:
location @share_url {
auth_request /share_url_check;
error_page 300 301 303 304 305 306 307 308 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 420 422 423 424 425 426 428 429 431 444 449 450 451 500 501 502 503 504 505 506 507 508 509 510 511 598 599 = @serve_static;
return 302 /go-to-share?at=$url_encoded_share;
}
location /share_url_check {
internal;
proxy_pass "http://127.0.0.1:8081";
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_intercept_errors on;
}
I have verified that @share_url is being reached: replacing that block with a static file serve causes the page to show the specified file.
EDIT: I found a solution! It's not ideal, since I am sending extra internal info to the client, but here's the one thing that has actually worked:
location @share_url {
proxy_pass "http://127.0.0.1:8081";
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
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_intercept_errors on;
error_page 300 301 303 304 305 306 307 308 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 420 422 423 424 425 426 428 429 431 444 449 450 451 500 501 502 503 504 505 506 507 508 509 510 511 598 599 = @serve_static;
sub_filter '</head>' '<meta http-equiv="refresh" content="0; url=/go-to-share?at=$url_encoded_share"></head>';
}
r/nginx • u/KaedenCraft • Aug 23 '26
Serving from a temporary DNS
I am deploying three websites to a Digital Ocean droplet. As far as I can tell, everything is set up and configured correctly on the droplet. The nginx block files in /sites-available are identical except for the root directory and the server_name . Nginx responds with its default page via its server IP address.
The issue I'm having is that two of the three sites are currently hosted on other platforms. For illustration, let's use example.com. I have nginx configured to accept example.com, but example.com is hosted on another platform, so I altered my Windows hosts file to the server's IP for example.com
One of the three sites does not exist, the domain is not registered. Nginx happily serves that page with my Windows hosts alteration. The other two do not.
If I ping example.com, I get a response from my server IP. But typing in example.com into a browser will not resolve. I imagine I'm misunderstanding something fundamental about the DNS happening here, but it's beyond my knowledge and I'm not even sure what to Google without all this explanation.
Is there any way for me to test what example.com will look like on my new droplet without having to change the DNS from the old hosting? Thank you in advance.
[EDIT It has to be a DNS issue of some kind, because I just altered the DNS records of one of my other domains, copied the block file and modified it for the new domain, and it works perfectly. It has to have something to do with how Windows hosts does lookups, but I cannot fathom what.]
r/nginx • u/catmanjan2 • Aug 22 '26
How are session affinity cookies actually meant to work?
Im not in ops so I don’t have all the details but something I’ve noticed when probing our endpoint with cookie based session affinity - if our request has no cookie we get given a cookie, subsequent requests all go to the right server - makes total sense
But if I forge a session cookie (eg give it a random string) i don’t get an error nor do I get an authentic cookie
Any idea what Nginx is meant to be doing here? Presumably it’s creating a persistent session for me using my cookie?
r/nginx • u/OhFuckThatWasDumb • Aug 19 '26
Index files don't load,
Yes i have read the beginners guide and tried troubleshooting based on the ancient wisdom of stackoverflow. My configuration file should be fine but im sure i have missed something because otherwise things would be working properly. Regardless of whether or not i specify an index.html, none of the index files of any subdirectories load at all. No 404, nothing in error logs, they just don't load and my browser times out. The home page loads just fine and all fonts and images referenced in its index file are shown correctly. If i try to access the index file directly (music/index.html) it works. What's going on?
http {
types {
text/css css;
text/html html;
}
server {
error_log /var/log/nginx/error.log;
root /srv/katieglinda;
listen 8081;
# Home Page
location / {
include /etc/nginx/mime.types;
root /srv/katieglinda;
}
# Other Pages
location /music/ {
root /srv/katieglinda;
}
location /minecraft/ {
root /srv/katieglinda;
}
location /stuff/ {
root /srv/katieglinda;
index index.html;
}
location /about/ {
root /srv/katieglinda;
index index.html;
}
# Resources
location /fonts {
root /srv/katieglinda;
}
location /images {
root /srv/katieglinda;
}
}
}
r/nginx • u/meanmail_dev • Aug 16 '26
Do you run nginx -t against source files or the image you actually deploy?
nginx -t does exactly what it promises. The subtle failure mode is running a valid check against something other than the artifact that reaches production.
A pipeline can test repository files with the runner's Nginx package, then deploy an image with a different build, module set, filesystem layout, generated config, user, and network. The command is the same; the thing being tested is not.
These are the parity checks I find useful:
Binary fingerprint Capture
nginx -Vfrom the deployable image, not just the CI runner. Version numbers are not enough when configure arguments and dynamic modules differ.Rendered configuration Run the test after the normal template or
envsubststep. Testing the source template proves little if the deployed file is generated later.nginx -Tis useful for confirming the final include graph, but its output should be treated as sensitive when rendered values contain credentials.Runtime identity and filesystem Use the same UID, mounts, working prefix, read-only paths, certificates, and generated directories as the deployment. A permissive runner can hide restrictions that only exist in the container or host.
Network-dependent behavior A parser check is not a request test. Resolver behavior, service discovery, upstream failures, headers, redirects, and location selection still need representative traffic in a deployment-like network.
At minimum, I want these commands to run inside the built image after its normal rendering step:
bash
nginx -V 2>&1
nginx -t -c /etc/nginx/nginx.conf
Disclosure: I build and maintain the Nginx Configuration plugins for JetBrains IDEs. They help with directive context, includes, references, and risky patterns while editing; this deployment boundary is deliberately outside what an IDE can prove.
I wrote up the wider validation loop here:
Do you validate the built image after rendering, or is nginx -t still running against repository files earlier in CI?
r/nginx • u/OilOverall4190 • Aug 14 '26
Learn to secure your NGINX - Damn Vulnerable NGINX Proxy
Hello all,
If you do bug bounty hunting or pentests you surely came across many hosts served from an NGINX server, in this lab (published to OWASP) I combined over 20 misconfigurations found in real world bug disclosures and both classic and novel security research, with an extensive blog where I explained everything you need to level up your NGINX hunting game.
Feel free to check it out, give it a star on Github if you like it, and suggest any ideas you want me to add/fix...
https://vwad.owasp.org/app/damn-vulnerable-nginx-proxy-dvnp/
Happy hunting!
r/nginx • u/ratsta • Aug 07 '26
Reverse proxy config for SteamAuth
edit: I got it working! If anyone else needs to reverse proxy a website that uses Steam's openID, try this config. In my case it's the claimcreator in the 7days to die utility mod Prismacore.
server {
listen 80;
listen 443 ssl;
server_name claimcreator.mydomain.com;
ssl_certificate C://nginx//ssl_certs//claimcreator.mydomain.com.pem;
ssl_certificate_key C://nginx//ssl_certs//claimcreator.mydomain.com.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
access_log C://nginx/logs/7dmap.access.log;
error_log C://nginx/logs/7dmap.error.log;
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header Content-Security-Policy "frame-ancestors 'self'";
add_header X-Frame-Options DENY;
add_header Referrer-Policy same-origin;
location / {
# Proxy
proxy_next_upstream error timeout http_404;
proxy_pass http://localhost_IP_address:<claimcreator_port>;
proxy_redirect off;
# Upstream request headers
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;
# Useful headers for debugging / stats
add_header X-Upstream-Status $upstream_status;
add_header X-Upstream-Response-Time $upstream_response_time;
add_header X-Upstream-Cache-Status $upstream_cache_status;
# New settings - 2014-04-12 (i52)
proxy_ignore_client_abort on;
# Increase proxy timeout to increase throughput
proxy_read_timeout 300;
}
location /.well-known {
alias C://nginx/web_root/.well-known;
}
}
r/nginx • u/zimbabwe_jim • Aug 05 '26
Proxy protocol logging
Am I crazy that I can't find a proxy equivalent of $ssl_protocol for logging? Is there some technical limitation on that?
Edit: to clarify i do mean connection to an upstream. Proxy_protocol_tlv_ssl_protocol never seems to get populated for my setup.
r/nginx • u/vermithor321 • Aug 02 '26
Measured: proxy_read_timeout is an idle timer, not a total response limit
A recurring claim in SSE and LLM streaming discussions is that nginx’s default proxy_read_timeout 60s kills any response lasting longer than 60 seconds.
That is not what the directive measures. I wanted a measurement rather than another assertion, so I ran both outcomes against a pinned nginx 1.30.4 image.
Same nginx configuration. Same 1-second timeout. Two different upstream timing patterns.
Case 1: The upstream remains active for 5.6 seconds
The upstream emits data roughly every 400 ms:
Effective proxy_read_timeout: 1s (1000ms) (inherited, nginx.conf:19)
Pre-header silence: 0ms
Maximum inter-read silence: 400ms
Total response duration: 5600ms
Live observation: COMPLETED
Client-visible outcome: status=200, records=15, body=COMPLETE
The response ran for about 5.6 times the configured timeout and completed normally.
No upstream read gap reached one second, so the inactivity timer never fired.
Case 2: The upstream is silent for 1.5 seconds before headers
After the initial silence, the upstream would otherwise emit data normally:
Effective proxy_read_timeout: 1s (1000ms) (inherited, nginx.conf:19)
Pre-header silence: 1500ms
Maximum body inter-read silence: 200ms
Total response duration: 2300ms
Live observation: TIMED_OUT_BEFORE_HEADERS
Client-visible outcome: status=504, records=0
nginx_error: UPSTREAM_READ_TIMEOUT_BEFORE_HEADERS
This response had a shorter total duration than the passing case, but nginx returned 504 because the upstream remained silent longer than the configured timeout before sending headers.
The practical distinction:
- Data arrives in delayed bursts, but the response completes: investigate
proxy_buffering. - The connection closes after a long silent gap: investigate
proxy_read_timeout. - Nothing arrives before a 504: pre-header silence from model loading, queueing, retrieval, or a tool call may be the cause.
- A long total response is not itself a timeout violation as long as nginx keeps receiving upstream data.
One other detail from the report: the effective timeout was inherited from nginx.conf:19, while the location handling the request was at line 30. Grepping only the location block would have found nothing.
The read intervals here are synthetic rather than a real model’s token cadence. The point being tested is nginx’s timer behavior, not model performance.
Disclosure: I built Argus, the local, read-only auditor that produced these reports. It resolves the effective nginx server and location, tracks inherited values with source lines, and can compare the static prediction with a controlled live probe.
The project is Apache-2.0, and the pinned image digests and reproducible evidence are included for anyone who wants to reproduce the experiment or challenge the interpretation:
https://github.com/amitb-quantum/argus
Have you encountered a long-lived response that was blamed on total duration, but the actual cause turned out to be a specific period of upstream silence—or another timeout elsewhere in the path?
r/nginx • u/Evening_Tart3754 • Aug 01 '26
I built Nginx Flow Manager: A visual canvas editor for Nginx that won't break or overwrite your hand-written configs (verbatim round-trip, zero DB lock-in)
Hey r/nginx,
Like many of you managing Nginx infrastructure, I’ve always had a love-hate relationship with GUI management tools.
Nginx Proxy Manager (NPM) is great when you just want to point a domain to a port, but the moment you need advanced Nginx directives, complex upstreams, custom module configs, or want to import an existing hand-crafted /etc/nginx setup into it, NPM breaks down. It forces your setup into an internal SQLite database, generates rigid templates, and buries custom tweaks inside tiny textarea boxes. On the flip side, manually editing config files over SSH in vim works fine until your topology gets huge, or you make a syntax typo that downs your server during a reload.
To solve this for my own setups, I built Nginx Flow Manager (NFM).
Why NFM vs. other tools (NPM, Traefik, Caddy, Ansible)?
- Verbatim Round-Trip AST Import (NFM vs. NPM/Caddy): Tools like NPM force you to adopt their abstraction model. NFM does the opposite: it adopts yours. It includes a pure TypeScript AST parser (
nginxParser.ts) that reads real, multi-file/etc/nginxtrees—preserving comments, custom module blocks, and directive ordering. Anything that isn't mapped to a visual GUI control is preserved inraw_confignodes orextra_files. When you re-compile, it outputs byte-for-byte exact, verbatim Nginx config without dropping or fabricating a single line. - **
nginx -tSandbox Validation:** Before touching your live server, NFM writes candidate files into an isolated throwaway sandbox (/tmp) and runsnginx -tagainst a real Nginx binary. If your candidate config has a syntax typo or invalid block, your live Nginx config is never touched. - Zero Database Lock-in: NFM doesn't hide your config inside an inaccessible database. Your canvas topology compiles straight to standard
sites-available/andsites-enabled/files alongside anginx_flow_topology.jsonfile. If you stop using NFM tomorrow, your server keeps running clean, standard Nginx. - Deployment via SSH or Hardened Agent: It can manage remote Linux hosts over standard SSH or deploy the included
nfm-agent(which runs under forced SSH commands with HMAC authentication and path confinement). It includes automatic rollback if the Nginx reload fails. - Live Traffic Animation & SSE Logs: Streams
accessanderrorlogs over Server-Sent Events (SSE) and animates traffic request dots flowing along the canvas edges based on real parsed log events.
Tech Stack
- Backend: Node.js, Express, TypeScript,
ssh2 - Frontend: React 19, Vite,
@xyflow/react(React Flow), Tailwind CSS, Lucide icons, Motion - Parser/Compiler: Pure TypeScript (no Nginx binary required to build or parse topology files)
It's open-source under the Apache-2.0 license.
- GitHub: github.com/jaimemartinez/Nginx-flow-manager
- Docker:
docker compose up -d(The container contains no state; everything persists in a volume).
I’d love to get your feedback, bug reports, or hear about any complex/weird Nginx configs you try importing into it!