advanced_config is admin-only, but other fields are written into nginx
config as-is and the schema barely limited them. A non-admin could add
any directive through them:
- location path, forward_host and forward_path (any non-empty string)
- proxy host forward_host (a quote breaks out of set $server "...")
- redirection forward_domain_name and stream forwarding_host (their
pattern allowed ";", e.g. "example.com; return 200; #")
- access list IPv6 addresses ("%.+" zone id allowed "::1%x; allow all")
- domain names (a newline got out of the header comment)
Validation
- New lib/nginx-values.js holds the rules. Values can't contain
characters that end or change an nginx token: whitespace, control
characters, ; { } " ' # \, and no $ in a host. Location paths allow a
modifier and an unquoted or double-quoted URI, so =, ~, ~* and ^~
locations and quoted regexes keep working.
- The API schemas use the same patterns, including the certificate
schema's own copy of the domain rule. A test fails if they drift.
- generateConfig checks stored values again, so rows saved before these
rules can't inject either. The check is about nginx structure only:
a stored value the API would now reject, but which can't add
directives, still renders. If a value is unsafe, nothing is written
and the host is marked offline with the reason in nginx_err. The same
check covers per-location access lists and the Let's Encrypt request
config.
- htpasswd user names can't contain ":" or control characters, which
corrupted the file.
Fixes along the way
- IPv6 client addresses: drop zone ids, which nginx's allow/deny
rejects anyway, and fix broken escapes (literal "d" for digits, an
unescaped ".").
- Rewrite the domain rule used by redirection and stream hosts so it
can't backtrack exponentially. It accepts the same values.
- Pattern errors still say "must match pattern", but no longer quote
the regex.
Tests
- backend/test/nginx-values.test.js, run by `yarn test` and in
scripts/ci/test-and-build. Real request schemas reject an injection
set and accept valid values on every endpoint. Accepted values,
rendered through the real templates and parsed using nginx's
tokenizer rules, keep the same statement structure. The check
refuses unsafe stored rows.
- NginxValues.cy.js: injection attempts from a non-admin get 400, and
hosts using valid edge-case values come up with nginx_online true.
Security
- Check advanced_config permissions before creating a certificate on
host update (proxy, redirection and 404 hosts). A refused request
previously requested a Let's Encrypt certificate first, leaving an
orphan certificate and using up rate limit.
- Only trust X-NPM-Peer-Addr when it comes with a secret generated at
container start. The admin nginx sends both headers. Any proxy host or
stream forwarding to 127.0.0.1:3000 was also a loopback peer, so it
could forge the header and get a fresh rate-limit bucket on every
request. Without the secret (dev, or a mounted production.conf), the
header is ignored.
- Add a login limit per IP across all accounts (30 failed attempts a
minute) alongside the per-account one. Before this, one address could
spray passwords over any number of accounts. Behind a shared proxy,
everyone using that proxy shares this limit.
- Stop trusting X-Real-IP from loopback. It was only there for an older
version of the rate limiter, and it let a stream host forwarding to a
local proxy host forge the client IP and get past IP access lists.
Loopback now behaves as it does on master.
Correctness
- Refuse to delete a certificate that hosts still use (400, with counts
per host type). Their configs include its files, and a successful
revoke deleted them anyway, which broke nginx -t. Otherwise the
certificate is revoked and its files are removed even if the revoke
fails.
- Let non-admins clear a location's advanced config. Deleting the
location was already allowed and has the same effect, and a location
deleted and re-added at the same path arrives with an empty config,
which was refused.
- Clear proxy host grants (meta.proxy_host_ids) when visibility isn't
"user" or proxy hosts are hidden, and drop ids of deleted hosts.
Before, hidden grants came back when proxy hosts were shown again.
The backend enforces this; the permissions modal also stops sending
them.
- 404 host and redirection host modals now wait for the current user
to load, so an admin's advanced config change isn't dropped if they
submit before it loads.
Tests
- NonAdminHosts.cy.js: refused change doesn't create a certificate,
location rename and clear rules, grant clearing.
- Certificates.cy.js: deleting a certificate that is in use.
- Add in-memory sliding window rate limiter for auth token endpoints with CI/test bypass and successful request exclusion
- Add user_permission meta column migration and Objection model configuration
- Support scoping allowed proxy hosts per user when in 'Created Items Only' visibility mode
- Restrict advanced nginx configuration editing on hosts to admin users only
- Fix user permissions route ID parsing and omit id from permission update payloads
- Add allowed proxy hosts selector in permissions modal and display permissions summary badges in users table
Nginx Proxy Manager had no way to inspect application or nginx logs
from the web UI - admins had to shell into the container or read
`docker logs`. This adds an admin-only Logs page that can tail:
- the backend application log, now also mirrored to
/data/logs/backend.log (in addition to stdout) and rotated by the
logrotate timer that already runs every 2 days
- the Let's Encrypt/certbot log, which certbot already writes to
/data/logs/letsencrypt.log via its existing --logs-dir flag
- per-host nginx access/error logs (proxy, redirection, 404 and
stream hosts), with the file path always resolved server-side from
a validated host_type enum + numeric host_id, never from client
input
Reads use a reverse chunked scan (64KB chunks, capped at 5MB scanned
per request) instead of loading whole files into memory, and the
frontend polls every 5s only while the tab is focused and "Live" is
on, so this stays cheap on both CPU and memory. No new runtime
dependencies were added on either side.
Purely additive: two new admin-only endpoints
(GET /api/logs/sources, GET /api/logs/tail), no existing behaviour
changed.
- Adds request logging in debug mode for some endpoints
- Moves certbot version determination to the startup scripts and removes
bash script encapsulation when installing plugins
- Revert loose domain validation, which was there for a specific reason
addressing CVE's
- Fix Cypress suite for cert generation
- Adds Cypress test that iterates over the entire certbot plugins list
and installs each one, ensuring at the very least that the install
works
- Fixed some plugins based on this
- (!) Still some work to do on this, hostinger is still broken at least
- Improved cypress tests for custom certs; they will generate on each
run instead of being baked in. The baked ones were due to expire soon
When Nginx is behind another proxy server (like CloudFlare or AWS ALB), the force-SSL
feature can cause redirect loops because Nginx sees the connection as plain HTTP
while SSL is already handled upstream. This adds a new boolean option to trust
the X-Forwarded-Proto header from upstream proxies.
Changes:
- Add `trust_forwarded_proto` column to proxy_host table (migration)
- Update model and API schema to support the new boolean field
- Modify force-ssl Nginx template to check X-Forwarded-Proto/X-Forwarded-Scheme
- Add map directives in nginx.conf to validate and sanitize forwarded headers
- Add advanced option toggle in frontend UI with i18n support (EN/ZH)
- Set proxy headers from validated map variables instead of $scheme
This allows administrators to control SSL redirect behavior when Nginx is deployed
behind a TLS-terminating proxy.
- Added frontend translation for english
- Moved frontend api logic to hook and backend api space
- Added swagger schema for the new api endpoint
- Moved backend logic to its own internal file
- Added user agent header to github api check
- Added cypress integration test for version check api
- Added a memory cache item from github check to avoid hitting it too
much
Should solve error `data/forward_scheme must be equal to one of the allowed values` when configuring a Redirection Host with scheme set to `auto`. #4074
- /schema now returns full openapi/swagger schema
- That schema is used to validate incoming requests
- And used as a contract in future integration tests
- Moved route files up one level
- Fixed incorrect 404 reponses when getting objects
- Fixed saving new objects and passing jsonschemavalidation