NginxProxyManager / NginxProxyManager/nginx-proxy-manager

Startup fails when DNS provider credentials contain single quotes / shell metacharacters

Open Beginner friendly
#5,445 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
TypeScript
Stars
34.2k
Forks
3.9k
Avg merge
21h 12m
Merged PRs (30d)
20

Description

Checklist

  • Have you pulled and found the error with jc21/nginx-proxy-manager:latest docker image?
    • Yes
  • Are you sure you're not using someone else's docker image?
    • Yes
  • Have you searched for similar issues (both open and closed)?
    • Yes

Describe the bug
Nginx Proxy Manager fails during backend startup when stored DNS provider credentials contain shell-significant characters.

In my case this happened with the Strato DNS challenge. The password contained a single quote ' and a pipe |.

During startup, NPM recreates /etc/letsencrypt/credentials/credentials-<id>. That write path appears to use a shell echo '...' > file command. If the credentials contain a single quote, shell quoting breaks. If the remaining value contains |, /bin/sh interprets it as a pipe
and startup aborts.

This causes the backend to crash before startup completes.

Relevant log excerpt:

❯ Starting backend ...
[4/1/2026] [11:34:44 AM] [Global   ] › ℹ  info      Using Sqlite: /data/database.sqlite
[4/1/2026] [11:34:44 AM] [Migrate  ] › ℹ  info      Current database version: none
[4/1/2026] [11:34:44 AM] [Certbot  ] › ▶  start     Installing strato...

node:internal/process/promises:394
    triggerUncaughtException(err, true /* fromPromise */);
    ^
[Error: /bin/sh: 2: Syntax error: "|" unexpected
] {

This does not look Strato-specific. It should likely affect any DNS provider credentials containing characters that are unsafe in shell-quoted strings.

Nginx Proxy Manager Version
2.14.0

To Reproduce

1. Configure a Let's Encrypt DNS challenge certificate
2. Use DNS provider credentials where the secret contains a single quote '
3. Include another shell metacharacter after that, for example |
4. Restart the container
5. Observe backend startup failure

Expected behavior
Credentials should be written safely regardless of password contents. Special characters in secrets should not break startup.

Screenshots
N/A

Operating System

docker --version
Docker version 27.1.2-qnap8, build b8cbe19


  Additional context
  A local workaround/fix was to stop writing the credentials file through a shell command and instead write it directly from Node.js, for example with fs.writeFileSync(...).

  That avoids failures with ', |, backslashes, $, and multiline values.

  Possible fix
  The credentials bootstrap in setup.js should avoid shell-based file creation.

  Instead of building a shell command that writes the credentials file via echo '...' > file, write the file directly from Node.js. That avoids shell parsing entirely and should safely handle ', |, $, backslashes, and multiline secrets.

  For example, the startup code could do something like this:

  import fs from "node:fs";

  // Make sure credentials file exists
  const credentials_loc = `/etc/letsencrypt/credentials/credentials-${certificate.id}`;
  if (typeof certificate.meta.dns_provider_credentials === "string") {
  	promises.push(
  		Promise.resolve().then(() => {
  			fs.mkdirSync("/etc/letsencrypt/credentials", { recursive: true });

  			try {
  				fs.writeFileSync(credentials_loc, certificate.meta.dns_provider_credentials, {
  					flag: "wx",
  					mode: 0o600,
  				});
  				fs.chmodSync(credentials_loc, 0o600);
  			} catch (err) {
  				if (err?.code !== "EEXIST") {
  					throw err;
  				}
  			}
  		}),
  	);
  }

  This preserves the current "create only if missing" behavior, but removes the shell quoting problem entirely.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in setup.js at the DNS provider credentials bootstrap and trace how the credentials file is created during backend startup. Reproduce the issue with credentials containing a single quote, pipe, backslash, dollar sign, or multiline value; done means startup succeeds and the existing create-only-if-missing behavior remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, node.js, typescript
Domain
backend, security
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.