A terminal UI (TUI) client for Nginx Proxy Manager and its NPMplus fork. Manage proxy hosts, redirections, streams, access lists and certificates over the REST API — straight from an SSH terminal, no web UI required.
Status: under active development.
Note: Built in pair-programming with AI assistance; the code is largely human-reviewed and tested.
- Terminal-native TUI — runs entirely in the terminal (over SSH, no web UI needed): responsive layout, keyboard-driven navigation, help overlay.
- NPM-compatible — works with Nginx Proxy Manager and its NPMplus fork over the REST API;
the variant is auto-detected (or force it with
--variant). - Manage your reverse proxy — proxy hosts, redirections, dead (404) hosts, streams and access lists with full CRUD; browse users, the audit log and settings.
- Certificate handling — issue Let's Encrypt certificates (HTTP & DNS challenge), upload custom certificates, renew and delete.
- Multi-language ready — ships with English and Czech, built so more languages can be added with no code changes.
lazynpmplus is an interactive TUI client (not a server), so it always needs a terminal —
note the -it on docker run and the run --rm (not up) for compose.
Pre-built multi-arch images (linux/amd64 + linux/arm64) are published on every release to
two registries:
- GitHub Container Registry
—
ghcr.io/mwarcz/lazynpmplus(primary) - Docker Hub —
docker.io/mwarcz/lazynpmplus(mirror)
# Pull (either registry works):
docker pull ghcr.io/mwarcz/lazynpmplus:latest
# docker pull docker.io/mwarcz/lazynpmplus:latest
# Run against an existing NPM instance:
docker run --rm -it ghcr.io/mwarcz/lazynpmplus:latest \
--server http://192.168.1.100:81 --identity admin@example.comAvailable tags: latest, plus version tags such as v1.2.3, 1.2 and 1.
Run lazynpmplus alongside a complete Nginx Proxy Manager instance in one stack; the client
reaches NPM over the internal network. See docker/compose/ for the
full walkthrough.
docker compose -f docker/compose/inline-env/compose.yml up -d
docker compose -f docker/compose/inline-env/compose.yml run --rm lazynpmplusThe compose example ships three sub-variants that differ only in how the client is configured — pick the one that matches how you manage settings:
| Variant | Configure via | Use when |
|---|---|---|
inline-env/ |
environment: in compose.yml |
Quick demos; one file, nothing else to manage. |
dotenv/ |
env_file: → local .env |
Keep credentials out of compose.yml / git. |
config-file/ |
mounted lazynpmplus.yml |
You manage app config as versioned files. |
Requires Go ≥ 1.25.
git clone https://github.com/mwarcz/lazynpmplus
cd lazynpmplus
make build # compiles ./lazynpmplusFor a standalone Docker build (no published image), see
docker/standalone/.
lazynpmplus targets the Nginx Proxy Manager REST API v2.x. NPMplus is a fork that exposes the same API version, so the same range applies to both.
| lazynpmplus | Supported NPM API |
|---|---|
0.x |
2.12–2.15 (NPM); NPMplus latest |
The tool's version is independent of NPM's. lazynpmplus uses its own SemVer and is not synced to the NPM version number — NPM and NPMplus release on different schemes, and the tool needs to version its own fixes and features separately. Compatibility is expressed by the range above, not by the version number.
The dashboard shows the detected server version; if it falls outside the tested range, a non-blocking warning is shown (the app keeps working).
The two forks differ in how they expose the admin/API on port 81, which affects --server:
| Admin/API on port 81 | --server |
TLS verification | |
|---|---|---|---|
| jc21 NPM | plain HTTP | http://host:81 |
default (insecure_tls: false) |
| NPMplus | HTTPS, self-signed cert | https://host:81 |
--insecure / insecure_tls: true |
NPMplus ships a self-signed certificate by default, so without --insecure
(LAZYNPMPLUS_INSECURE_TLS=true) the connection fails certificate verification. If you put
NPMplus behind a trusted certificate, you can leave verification on.
Settings are merged with the following precedence (highest wins):
- CLI flags (
--server,--identity,--lang, …) - Environment variables (
LAZYNPMPLUS_*) - Local
./.lazynpmplus.yml - Global
~/.config/lazynpmplus/.lazynpmplus.yml
| Flag | Env | Default | Description |
|---|---|---|---|
--server |
LAZYNPMPLUS_SERVER |
— | Base URL of NPM (e.g. http://127.0.0.1:81) |
--identity |
LAZYNPMPLUS_IDENTITY |
— | Login email |
--secret |
LAZYNPMPLUS_SECRET |
— | Password (prefer env or prompt) |
--variant |
LAZYNPMPLUS_VARIANT |
auto |
Force token bearer: npm (JWT body), npmplus (session cookie), or auto (detect) |
--insecure |
LAZYNPMPLUS_INSECURE_TLS |
false |
Skip TLS verification |
--lang |
LAZYNPMPLUS_LANG |
auto-detect | en or cs (or a code added via --locale-file) |
--locale-file |
LAZYNPMPLUS_LOCALE_FILE |
— | Path to an external locale YAML; file name = language code (e.g. de.yml). Overrides a built-in language of the same code |
--lang-strict |
LAZYNPMPLUS_LANG_STRICT |
false |
Disable the English fallback; missing keys render as the raw key (handy while translating) |
--theme |
LAZYNPMPLUS_THEME |
— | Color theme (future use) |
--config |
— | — | Path to a config YAML (replaces defaults) |
--token-path |
— | ~/.config/lazynpmplus/token.json |
Token file path |
--log-level |
LAZYNPMPLUS_LOG_LEVEL |
— (off) | debug, info, warn or error; empty disables logging |
--log-file |
LAZYNPMPLUS_LOG_FILE |
~/.config/lazynpmplus/lazynpmplus.log |
Log file path |
--log-format |
LAZYNPMPLUS_LOG_FORMAT |
text |
text or json |
server: https://192.168.1.100:81 # NPMplus: HTTPS on 81 (jc21 NPM uses http://)
identity: admin@example.com
variant: npmplus
insecure_tls: true # NPMplus self-signed cert; false for jc21/trusted cert
language: cs
theme: default
log_level: warn # off when empty
log_format: text # text | json
# log_file: /var/log/lazynpmplus.logWarning: setting
secret:in a YAML config file stores your password as plaintext on disk. The application logs a warning when it detects this. Prefer supplying the password via theLAZYNPMPLUS_SECRETenvironment variable or the--secretflag (both avoid writing the value to a file), or simply type it on the login screen.
For a ready-to-mount example documenting every available option (with inline notes on what
each does), see
docker/compose/config-file/lazynpmplus.yml.
Tip: press
eon the dashboard to export the current settings — the effective configuration (after merging flags, env and files) is written to the working directory as a YAML config file or a.envfile. It doubles as a shortcut and as a way to audit what the app actually uses. The password is never written (only a commented placeholder).
Most config keys map to LAZYNPMPLUS_<UPPERCASED_KEY>. Mind the few exceptions: the
language env var is LAZYNPMPLUS_LANG (YAML key language), TLS is LAZYNPMPLUS_INSECURE_TLS
(YAML key insecure_tls), and --config / --token-path have no env equivalent — they
are flags only. See the table above for the exact env name of each option.
export LAZYNPMPLUS_SERVER=http://192.168.1.100:81
export LAZYNPMPLUS_IDENTITY=admin@example.com
export LAZYNPMPLUS_LANG=csThe login token is saved in ~/.config/lazynpmplus/token.json (mode 0600). NPM stores
a JWT; NPMplus stores a session cookie — token.json handles both. Tokens are stored
per-server: switching --server between instances keeps each session alive
independently without re-logging in. Passwords are never stored. The token is
refreshed automatically on 401.
Logging is off by default and is enabled by setting a level
(--log-level debug|info|warn|error). Records are structured (log/slog) and
written to a file — by default ~/.config/lazynpmplus/lazynpmplus.log
(mode 0600). Choose --log-format json for machine parsing.
The UI runs in the terminal alternate screen, so writing logs to stdout/stderr while the TUI is active corrupts the display. Prefer the log file. Secrets (password, token, session cookie) are never logged.
lazynpmplus --server http://127.0.0.1:81 --log-level debug
tail -f ~/.config/lazynpmplus/lazynpmplus.log# Authenticate and open the dashboard
./lazynpmplus --server http://127.0.0.1:81 --identity admin@example.com| Key | Action |
|---|---|
↑ / ↓ |
Move selection |
← / →, Tab / Shift+Tab |
Switch tab (forms) |
Enter |
Confirm / open detail |
Esc |
Back / cancel |
1–9 |
Open section (dashboard) |
n |
Create new item |
e |
Edit selected item |
d |
Delete selected item |
Space |
Enable / disable (host sections) |
r |
Refresh |
l |
Log out |
? |
Help overlay |
q / Ctrl+C |
Quit |
| # | Section | Read / Write |
|---|---|---|
| 1 | Proxy Hosts | ✅ |
| 2 | Redirections | ✅ |
| 3 | Dead Hosts | ✅ |
| 4 | Streams | ✅ |
| 5 | Access Lists | ✅ |
| 6 | Certificates | ✅ |
| 7 | Users | read-only |
| 8 | Audit Log | read-only |
| 9 | Settings | read-only |
| Language | Code |
|---|---|
| English | en |
| Czech | cs |
The list grows as locale files are added under internal/i18n/locales/.
The interface language is resolved in this order:
--langflagLAZYNPMPLUS_LANGenvironment variablelanguagekey in the config file- Host locale (
$LANG,$LC_MESSAGES, etc.) - Fallback to English
Adding a built-in language: create a new file in internal/i18n/locales/
with the same keys as the reference en.yml. It is picked up automatically — no
other code changes are needed.
You can load a translation from a file on disk, without rebuilding the binary — useful for trying out a new language or shipping your own wording when you don't agree with the bundled translation:
# Add a brand-new language (file name = language code):
lazynpmplus --locale-file ./de.yml --lang de
# Override the bundled Czech wording (file must be named cs.yml):
lazynpmplus --locale-file ./cs.yml --lang cs- The file name is the language code (
de.yml→de,cs.yml→cs); name the file accordingly, then select it with--lang <code>. - An external file with the same code as a bundled language overrides it, so you can patch existing translations.
- Use the reference
internal/i18n/locales/en.ymlas the list of keys to translate.
Spotting untranslated keys: by default a key missing from your file falls back
to English. Add --lang-strict while translating to disable that fallback —
missing keys then render as the raw key, so you can see exactly what's left to
fill in.
The Quick start above covers the common paths. All examples live in
docker/, with a README in every folder; docker/README.md
is the full walkthrough — both deployment shapes (standalone vs compose), token persistence,
and the maintainer notes for publishing images to GHCR + Docker Hub.
- Nginx Proxy Manager — the original web UI
- NPMplus — community fork with additional features