A single Go binary that emulates the Proxmox subscription endpoint for labs and homelabs, and doubles as the client tool to point a Proxmox host at it.
For private / internal test environments only. This tool does not contact Proxmox' real servers and changes nothing on a host unless you explicitly run the client steps there. Removing the subscription nag on a production host bypasses a commercial subscription - production deployments need a real Proxmox subscription.
Proxmox verifies online keys by POSTing to shop.proxmox.com/.../verify.php. The
response is only "signed" with md5(SHARED_KEY_DATA + check_token), where the
constant is public (open-source proxmox-subscription). The proxy reproduces a
valid response locally. Point shop.proxmox.com at the proxy, trust its
certificate, and Proxmox writes status = active.
Hosts that contact the proxy are auto-registered as pending and only become
active once an admin approves them (or via auto_approve).
Scope: this only fakes the local subscription status (the nag screen and
status = active). It does not unlock the Proxmox enterprise apt repository - that is gated server-side by Proxmox (subscription key + IP checks atenterprise.proxmox.com), which a local proxy cannot bypass. Use the freeno-subscriptionrepo for package updates.
Auto-detects your CPU architecture and package format (.deb/.rpm/.apk) and
installs the matching package (including shell completion and the systemd
service) from the latest release:
curl -fsSL https://raw.githubusercontent.com/TheMeinerLP/proxmox-license-proxy/main/install.sh | shWant just the CLI, with no service and no config (e.g. to run serve by hand
on a single host)? Use CLI-only mode - it installs the binary to
/usr/local/bin plus shell completions:
curl -fsSL https://raw.githubusercontent.com/TheMeinerLP/proxmox-license-proxy/main/install.sh | PMOX_CLI_ONLY=1 shPin a version with PMOX_VERSION (set it on the sh, not the curl, when
piping):
curl -fsSL .../install.sh | PMOX_VERSION=2.0.0 sh. Or review the script first:
curl -fsSL .../install.sh -o install.sh && less install.sh && sh install.sh.
All artifacts are also attached to every GitHub Release
(linux/darwin, amd64/arm64) for manual install - replace <ver> with the
release version.
The package installs a hardened systemd service, a dedicated pmox user and a
default config at /etc/pmox/config.yaml:
ARCH=amd64 # or arm64
curl -fsSLO https://github.com/TheMeinerLP/proxmox-license-proxy/releases/latest/download/proxmox-license-proxy_<ver>_linux_${ARCH}.deb
sudo apt install ./proxmox-license-proxy_<ver>_linux_${ARCH}.debThe service is enabled but not started on install - review the config first, then start it:
sudoedit /etc/pmox/config.yaml
sudo systemctl start proxmox-license-proxy
systemctl status proxmox-license-proxysudo dnf install ./proxmox-license-proxy_<ver>_linux_amd64.rpm
sudo apk add --allow-untrusted proxmox-license-proxy_<ver>_linux_amd64.apkdocker run -d --name pmox -p 443:8443 -v pmox:/data \
ghcr.io/themeinerlp/proxmox-license-proxy:latest
# or use the bundled compose file (builds locally):
docker compose up -dThe tar.gz bundles the binary plus a systemd unit and sample config under
contrib/:
curl -fsSL https://github.com/TheMeinerLP/proxmox-license-proxy/releases/latest/download/proxmox-license-proxy_<ver>_linux_amd64.tar.gz | tar xz
sudo install proxmox-license-proxy /usr/local/bin/git clone https://github.com/TheMeinerLP/proxmox-license-proxy && cd proxmox-license-proxy
CGO_ENABLED=0 go build -ldflags "-s -w" -o proxmox-license-proxy .There are two roles. They can be two machines or the same machine.
- Proxy host - runs the subscription server (
serve). Answers the subscription check. - Proxmox host - the PVE/PBS/PMG you want to show "subscribed". It must trust the
proxy's certificate and resolve
shop.proxmox.comto the proxy.
curl -fsSL https://raw.githubusercontent.com/TheMeinerLP/proxmox-license-proxy/main/install.sh | sh
sudoedit /etc/pmox/config.yaml # optional; enable auto_approve here if you like
sudo systemctl start proxmox-license-proxyIt serves HTTPS on :443 with a persistent self-signed cert (stable across
restarts/upgrades) and advertises itself on the LAN via mDNS.
# installs the binary, discovers the proxy via mDNS, trusts its cert, and points
# shop.proxmox.com at it. Prints the cert fingerprint so you can verify it.
proxmox-license-proxy client installSame machine as the proxy? Add --no-binary (the package already installed the
binary - this avoids a /usr/local/bin copy shadowing it):
proxmox-license-proxy client install --no-binary# on the PROXY host: mint a lab key (interactive; or --product/--level/--sockets)
proxmox-license-proxy subscription generate
# then tell Proxmox to use it (PBS shown; PVE/PMG have their own command)
proxmox-backup-manager subscription set pbsc-1ab1234567
# first contact registers the host as PENDING; approve it on the PROXY host:
proxmox-license-proxy server pending
proxmox-license-proxy server approve <serverid>
# ...or skip this by setting auto_approve in the config (see below).
proxmox-backup-manager subscription update # on the Proxmox hostclient install offers the mDNS-discovered servers and lets you pick which IP
(or localhost for a single host). List them anytime with client discover;
disable advertising with serve --mdns=false. Docker users: docker compose up -d
then run the subscription/server commands via docker compose exec proxy ....
Config precedence: flags > PMOX_* env > config file > defaults.
| Key (config.yaml) | Env | Default |
|---|---|---|
listen |
PMOX_LISTEN |
:443 |
log |
PMOX_LOG |
info |
registry_file |
PMOX_REGISTRY_FILE |
/etc/pmox/registry.json |
tls.mode |
PMOX_TLS_MODE |
auto (auto/files/http) |
hosts.file / hosts.ip |
PMOX_HOSTS_FILE / PMOX_HOSTS_IP |
/etc/hosts / - |
auto_approve.enabled / .private / .networks |
PMOX_AUTO_APPROVE_ENABLED |
false / false / - |
On a terminal, proxmox-license-proxy config init runs a guided wizard (TLS
mode, registry path, auto-approval and more) and writes the config for you; it is
the same wizard as setup server. Prefer a static, fully-commented scaffold to
edit by hand? Use config init --defaults (or see
config.example.yaml).
By default a host that first contacts the server is registered as PENDING and
must be approved with server approve. To skip that for trusted machines, enable
auto-approval by source IP - new (and still-pending) hosts from a trusted network
are approved on the spot, for any product (PVE/PBS/PMG). A BLOCKED/REJECTED
host is never re-approved automatically.
auto_approve:
enabled: true
private: true # trust RFC1918 / ULA / loopback / link-local
networks: ["100.64.0.0/10"] # optional extra CIDRsThe match is on the TCP source address, so put the proxy on the same L2/L3 as the hosts (no NAT between them) for it to be meaningful.
serve, status, doctor, subscription {add,generate,list,show,rm,set-due,export,import}
server {list,pending,approve,reject,block,rm},account {list,approve,block}client {install,enroll,uninstall,discover},cert {generate,install}hosts {enable,disable,status},config {init,show,path},version,completion
serve prints a startup summary (reachable URLs, TLS mode, CA fingerprint and
next steps) before it starts logging. Stuck? doctor runs read-only checks - a
shadowing binary on PATH, an unreadable registry, an expired/missing TLS cert, a
missing /etc/hosts entry or untrusted CA - and prints a hint for each. Both
doctor and version --check ask GitHub whether a newer release is out (a
best-effort, short-timeout call that never blocks the command if offline).
subscription generate mints a lab-only key: it is format-valid (so the emulation
works) but deliberately marked - the key carries a visible 1ab ("lab") signature
(e.g. pbsc-1ab879865b) and its product name is tagged
(LAB, proxmox-license-proxy - NOT FOR PRODUCTION), which Proxmox shows in its
subscription panel. The command prints a warning banner and requires confirmation.
Run interactively it asks for the product and level (and, for PVE, the CPU
socket count, since PVE keys encode [1248]); or pass --product/--level/--sockets
for scripts, e.g. subscription generate --product pve --level s --sockets 4 --yes.
Every subscription must carry the 1ab signature - subscription add (and the REST
API / import) reject any key without it. This guarantees the proxy can only
ever manage clearly-marked lab keys, never something mistakable for a real
subscription. The easiest path is subscription generate.
Guided by default. Run a management command without arguments on a terminal and it walks you through it interactively instead of erroring - no need to copy ids or keys around:
subscription/client(no subcommand) - open a guided menu that lists the actions, runs the one you pick and loops until you choose quitserver approve/reject/block- multi-select hosts from a listserver rm- pick the host to removesubscription show/rm/set-due- pick the key (and, forset-due, type the new date) from a promptsubscription add- prompts for the key (tip:subscription generatemints one)config init/setup server- a full wizard (TLS, registry, auto-approve, ...)cert generate/cert install- prompt for hostnames / the cert source, and show the fingerprint before touching the trust storehosts enable- confirms before editing/etc/hosts(and asks for the IP if unset)
The flags still work for scripts: approve/reject/block accept multiple
server ids; approve/reject also take --all (all pending hosts) and --note.
Read commands (status, subscription list/show, server list/pending) support
-o/--output table|json|yaml.
Destructive commands (subscription rm, server rm, hosts disable) prompt for
confirmation; pass -y/--yes to skip it. The registry keeps a .bak of the
previous state on every write.
Instead of minting a key by hand, a Proxmox host can enrol itself, like certbot
for the subscription nag. client enroll:
- detects the installed products (PVE/PBS/PMG - a host may run several),
- trusts the proxy CA (pinned by fingerprint) and creates an account key,
- registers an ACME account and orders a subscription per product
(auto-picking the PVE socket tier from the host's CPU sockets, so a
2-/4-socket host is not handed a 1-socket key
pvesubscriptionrejects), - installs each issued key with the product's own
… subscription settool.
# on the Proxmox host - first time, point it at the proxy
proxmox-license-proxy client enroll --server https://192.168.68.100You only pass --server the first time. enroll remembers the proxy via the
/etc/hosts redirect it writes, so later runs need no arguments:
proxmox-license-proxy client enroll # reuses the saved proxyIf the host was never set up at all, a bare enroll on a terminal kicks off
client install (discovers the proxy, trusts its cert, writes the redirect) and
then continues - so one command takes a fresh host from nothing to subscribed.
Issuance is gated on the account, not a guessed host id. A new account is
PENDING until an admin approves it (or it registered from an auto_approve
network); enroll prints the account id and exits, and you re-run it after:
# on the PROXY host
proxmox-license-proxy account list
proxmox-license-proxy account approve <thumbprint>The server can invalidate a subscription at any time (server/admin API or
the account itself); the host drops to unsubscribed on its next check.
The packages ship a certbot-style renew timer (disabled by default, since the same package runs on the proxy host too). On a Proxmox host, point it at the proxy and enable it:
# on the Proxmox host
sudo cp /etc/pmox/enroll.env.example /etc/pmox/enroll.env
sudo $EDITOR /etc/pmox/enroll.env # set PMOX_ENROLL_SERVER=https://<proxy>
sudo systemctl enable --now proxmox-license-proxy-enroll.timerThe timer runs client enroll daily. Because enroll is idempotent (the server
de-duplicates by account+product and re-setting an unchanged key is a no-op), a
run safely picks up a just-approved account, re-applies a re-issued key or
heals a cleared one — without touching anything when all is current. Every
client enroll flag has a PMOX_ENROLL_* env equivalent for enroll.env; the
unit stays inert until /etc/pmox/enroll.env exists (ConditionPathExists).
Note the ACME-issued subscriptions do not hard-expire: Proxmox re-checks
against the proxy on its own schedule and the proxy keeps answering active
until you revoke. The renew timer is about approval pickup and self-healing, not
a looming expiry.
Legacy (unversioned): POST /modules/servers/licensing/verify.php, GET /ca.crt,
GET /healthz, GET /readyz, GET /status, /api/subscriptions, /api/servers.
Versioned, ACME-style /api/v1 (account keys + JWS, RFC 8555-inspired):
GET /directory, GET /new-nonce, POST /new-account, POST /new-order
(issues keys), POST /subscriptions, POST /revoke; plus a bearer-token
/api/v1/admin/* for account/host approval and subscription revocation. See
docs/api-v1.md. The product is at v2.0.0; the API is at
its first version (v1) - the two version numbers are independent.
v2 consolidates config, the registry and the auto cert under /etc/pmox (the
registry was /var/lib/pmox/registry.json). Migration is automatic:
- Packages (
.deb/.rpm/.apk): the postinstall moves the old registry, backup and persisted cert across on upgrade. - Binary / manual installs: on its next start,
servecopies a pre-2.0 registry from/var/lib/pmoxto the configured location (it never overwrites existing data).doctorwarns if an un-migrated registry is found. - Docker: unaffected - the image pins
PMOX_REGISTRY_FILE=/data/registry.json.
The registry format is backward-compatible (the new fields are additive), so
existing subscriptions and approved hosts keep working. To migrate by hand:
mv /var/lib/pmox/registry.json* /etc/pmox/.
CI (GitHub Actions) is the source of truth - it runs tests (-race),
golangci-lint and a build on every push/PR. Locally:
go test -race ./...
golangci-lint run # same config as CI
go build ./...Commits follow Conventional Commits.
Release Please maintains a
release PR; merging it tags the release, after which GoReleaser publishes the
binaries + .deb/.rpm/.apk packages and a multi-arch image to ghcr.io.
See CONTRIBUTING.md for details.
This is for home labs and internal test clusters - not a way to dodge paying for production. If you run Proxmox in production, buy a subscription. See SECURITY.md for the full policy and how to report vulnerabilities.