Files
RustProxy/docs/NPM_MIGRATION.md
T
2026-10-06 15:12:59 +02:00

4.7 KiB

Nginx Proxy Manager migration

RustProxy ships scripts/migrate_npm.py for a one-time, dry-run-first migration of Nginx Proxy Manager (NPM) proxy hosts from its SQLite database.

The tool reads NPM only. It does not modify the NPM database and it never imports Let's Encrypt/private-key material. It logs in to the RustProxy administration API, reads the current revision, merges generated vhosts into the existing configuration, and writes only when --apply is supplied.

migrate_npm.py is the CLI entry point and requires the sibling modules scripts/migrate_npm_core.py and scripts/migrate_npm_api.py. They use only the Python standard library, so no pip packages are required. If the migrator is copied or mounted outside the RustProxy source tree, keep all three files in the same directory.

What is migrated

For each NPM proxy_host row:

  • domain_names -> RustProxy vhost hostnames, including leading *. wildcards;
  • forward_scheme, forward_host, forward_port -> one RustProxy backend;
  • allow_websocket_upgrade -> vhost WebSocket policy;
  • enabled -> vhost enabled state;
  • http2_support -> vhost HTTP/2 policy;
  • NPM hosts with a certificate/forced SSL -> RustProxy TLS listener. Certificate files are not copied: the application-managed RustProxy default certificate is used unless --certificate-id names an existing RustProxy inventory certificate;
  • ssl_forced -> RustProxy force_ssl=true. When HTTP and HTTPS listeners exist, both are attached so cleartext requests can receive RustProxy's 308 redirect to HTTPS. If no TLS listener exists, an SSL-forced NPM host is skipped instead of being imported with weaker semantics.

Migrated backends use allow_private=true because NPM commonly proxies to LAN or container origins. The migration tool follows RustProxy 0.22.1 and imports backend tls_verify=false unless you later enable verification explicitly. Use --npm-compatible-backend-tls when an NPM HTTPS origin depends on the common Nginx behavior of not verifying the upstream certificate; review that exception after migration.

The following are reported for manual review rather than guessed: NPM access lists, advanced Nginx snippets, custom locations, NPM cache settings and block_exploits. Existing RustProxy hostname ownership is never silently replaced; conflicting NPM rows are skipped and shown in the report.

SQLite location

The standard NPM SQLite path inside the container is /data/database.sqlite. On a Docker host with a bind mount, pass the corresponding host path. For a named volume, docker volume inspect shows its mountpoint.

Read the live mounted database rather than copying only database.sqlite from a running WAL-mode instance; copying without its WAL file can omit recent changes.

Dry run

Set the password in the environment or omit it and use the interactive prompt:

export RUSTPROXY_USERNAME=admin
export RUSTPROXY_PASSWORD='...'

python3 scripts/migrate_npm.py \
  --database /path/to/npm/data/database.sqlite \
  --rustproxy https://rustproxy.example.net:9090

The default run is read-only. It creates npm-migration-plan.json with mode 0600 and prints every imported/skipped NPM row plus manual-review warnings.

If the administration panel intentionally uses the application self-signed certificate and the script connects directly to it, add:

--insecure-admin-tls

Prefer --ca-file /path/to/ca.pem or a hostname with a trusted certificate where available.

By default the first enabled HTTP listener and first enabled TLS listener are used. For ssl_forced hosts both are selected when available. --force-https applies the same Force SSL policy to every imported host. Select explicit listener IDs when needed:

--http-listener public-http --https-listener public-https

Apply

After reviewing npm-migration-plan.json:

python3 scripts/migrate_npm.py \
  --database /path/to/npm/data/database.sqlite \
  --rustproxy https://rustproxy.example.net:9090 \
  --apply

The write uses the revision returned by GET /api/config; a concurrent RustProxy configuration change causes the API compare-and-swap to fail rather than overwrite a newer revision.

Running from a helper container

If the Docker host does not have Python 3, mount the NPM data read-only and mount the RustProxy source/script into a temporary Python container. For example, from the RustProxy directory:

docker run --rm -it \
  --network host \
  -v /path/to/npm/data:/npm:ro \
  -v "$PWD/scripts:/rustproxy-scripts:ro" \
  python:3.13-alpine \
  python /rustproxy-scripts/migrate_npm.py \
    --database /npm/database.sqlite \
    --rustproxy https://127.0.0.1:9090 \
    --insecure-admin-tls

Use --apply only after the dry-run plan is reviewed.