Skip to content

Proxy host save succeeds in DB and UI but no nginx config is generated (no "Building proxy host" log entry, empty /data/nginx/proxy_host/) #5532

@ludwigmeier

Description

@ludwigmeier

Summary

Creating a proxy host (via UI or REST API) succeeds in the database and shows green "Online" status in the UI, but no [Nginx] Building proxy host #N for: ... log entry is emitted and no .conf file is written to /data/nginx/proxy_host/. The proxy therefore never routes traffic and clients receive tlsv1 unrecognized name SNI alerts.

Only a [Nginx] Reloading Nginx log entry follows the save — the config-generation step in between is silently skipped, without any error.

Same behavior on :latest and :2.14.0 with completely fresh data directories. Cert uploads and Access List generation on the same instance work normally — only proxy host generation is affected.

Strongly suspected root cause: Docker Engine 29 with the new default containerd image store. Details under "Environment / Hypothesis" below.

Nginx Proxy Manager Version

Reproduced on both:

  • jc21/nginx-proxy-manager:latest (image built 2026-02-17)
  • jc21/nginx-proxy-manager:2.14.0 (image built 2026-02-17)

No newer release exists at the time of writing.

To Reproduce

Minimal compose:

services:
  npm:
    image: jc21/nginx-proxy-manager:2.14.0
    container_name: nginx-proxy-manager
    restart: unless-stopped
    ports:
      - "8080:80"
      - "8443:443"
      - "81:81"
    volumes:
      - /opt/docker/npm/data:/data
      - /opt/docker/npm/letsencrypt:/etc/letsencrypt
    environment:
      DISABLE_IPV6: 'true'
    extra_hosts:
      - "host.docker.internal:host-gateway"

Steps:

  1. Deploy with empty /opt/docker/npm/data and /opt/docker/npm/letsencrypt
  2. Log in with admin@example.com / changeme, set new admin user
  3. Hosts → Proxy Hosts → Add Proxy Host with the minimum:
    • Domain Names: test.example.com
    • Scheme: http
    • Forward Hostname/IP: host.docker.internal
    • Forward Port: 9000 (or any reachable upstream)
    • All checkboxes off, no SSL cert, no Access List, no Advanced config
  4. Save

Same result via the REST API:

TOKEN=$(curl -s -X POST http://localhost:81/api/tokens \
  -H "Content-Type: application/json" \
  -d '{"identity":"admin@example.com","secret":"NEW_PASSWORD"}' \
  | jq -r .token)

curl -X POST http://localhost:81/api/nginx/proxy-hosts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "domain_names": ["test.example.com"],
    "forward_scheme": "http",
    "forward_host": "host.docker.internal",
    "forward_port": 9000,
    "access_list_id": 0, "certificate_id": 0,
    "ssl_forced": false, "caching_enabled": false,
    "block_exploits": false, "advanced_config": "",
    "meta": {}, "allow_websocket_upgrade": false,
    "http2_support": false, "hsts_enabled": false,
    "hsts_subdomains": false, "locations": []
  }'

API returns HTTP 201 with the full proxy host object. UI lists it as Online. But no config file is generated.

Expected behavior

Container logs should show:

[Nginx] Building proxy host #1 for: test.example.com
[Nginx] Reloading Nginx

/data/nginx/proxy_host/1.conf should exist.

Actual behavior

Container logs only show:

[Nginx] Reloading Nginx

The Building proxy host line that normally precedes the reload is missing entirely. /data/nginx/proxy_host/ remains empty:

$ docker exec nginx-proxy-manager ls -la /data/nginx/proxy_host/
total 8
drwxr-xr-x 2 root root 4096 May 12 16:52 .
drwxr-xr-x 9 root root 4096 May 12 16:43 ..

nginx -T inside the container confirms only NPM's built-in defaults are loaded — no user-created proxy host configs.

No errors are logged anywhere:

$ docker logs nginx-proxy-manager 2>&1 | grep -iE "error|fail|denied|exception"
(no output)

Diagnostic log sequence

Container startup is clean — all migrations succeed, including the recent trust_forwarded_proto:

[Migrate] info  [initial-schema] Migrating Up...
... (all migrations OK) ...
[Migrate] info  [trust_forwarded_proto] proxy_host Table altered
[Setup]   info  Default settings added
[Global]  info  Backend PID 182 listening on port 3000 ...

Custom Certificate upload works (writes to disk, log entry present):

[SSL] info  Writing Custom Certificate: { id: 1, ... }

Access List creation works (writes file, log entry present, reload follows):

[Access] info  Building Access file #1 for: intern-only
[Nginx]  info  Reloading Nginx

Proxy host save fails silently (only the reload, no Building entry):

[Nginx]  info  Reloading Nginx

What I ruled out

Hypothesis Test Result
File permissions docker exec npm touch /data/nginx/proxy_host/test.conf Succeeds
Template corruption cat /app/templates/proxy_host.conf Intact, 1235 bytes
Container user mismatch docker exec npm id uid=0(root)
UI-only bug API call via curl Same silent failure
Custom Locations / Advanced config Minimal host with everything off Same silent failure
SSL cert / Access List interaction Minimal host with no SSL, no AL Same silent failure
NPM version Tested :latest and :2.14.0 on fresh data Same on both
Disk space df -h /data Plenty free
Parallel host nginx (still to verify by stopping host nginx)

Environment / Hypothesis

  • Docker Engine: 29.4.2 (April 2026), API 1.54
  • Docker Compose: v5.1.3
  • OS: Ubuntu Server 24
  • Architecture: x86_64
  • Deployed via Portainer Stack
  • Storage driver: default containerd snapshotter (Docker 29's new default)

Hypothesis: Docker Engine 29.0 (released March 2026) made the containerd image store the default for new installations. The NPM 2.14.0 image was built on 2026-02-17, i.e. before Docker 29 GA, so it has never been tested against the new default snapshotter.

The symptom — DB writes succeed, file writes to /data succeed for certificates and access lists, but the specific code path that generates /data/nginx/proxy_host/N.conf silently completes without writing or logging — is consistent with a subtle filesystem-semantics difference between the legacy graphdriver and the containerd snapshotter when running NPM's Node.js template-rendering code.

Proposed test for maintainers to confirm:

Set /etc/docker/daemon.json to:

{
  "features": {
    "containerd-snapshotter": false
  }
}

Then sudo systemctl restart docker and re-run the reproducer. If proxy host generation works under the legacy graphdriver but fails under the containerd snapshotter, the cause is confirmed.

Additional notes

  • The "Online" status badge in the UI is misleading: it only checks upstream reachability, not whether the proxy itself is routing traffic.
  • The database insert succeeds (host appears in UI and is returned by GET /api/nginx/proxy-hosts).
  • The certificate writing step succeeds (/data/custom_ssl/... is populated).
  • The access list file generation succeeds (/data/access/... is populated).
  • Only the proxy host config generation step (internalNginx.configure(model, 'proxy_host') or whatever the equivalent in the current codebase is) appears to be silently skipped without an error.

If helpful, I can also provide:

  • Full container startup log
  • Output of nginx -T inside the container
  • SQLite DB dump showing the orphaned proxy host record
  • Docker info / docker system info output

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions