NginxProxyManager / NginxProxyManager/nginx-proxy-manager
Startup fails when DNS provider credentials contain single quotes / shell metacharacters
Nobody has claimed this yet.
- 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:latestdocker 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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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