Skip to content

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

@MSahl

Description

@MSahl

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions