NixOS PR: NixOS/nixpkgs#543254
A native Nix package and NixOS module for Proxmox Backup Server
(PBS). No Debian container, no dpkg runtime, just a regular Nix derivation and a
systemd-based service.
The package builds PBS 4.2.4 from the upstream Proxmox git sources, reusing the same
source set and Cargo lock basis as the nixpkgs proxmox-backup-client package. The web UI
assets are pulled from the official Proxmox .deb archives and stitched together at build
time.
Important
Feedback wanted. This port works for me, but it needs more eyes. I'm filing a tracking issue per untested feature. If you run PBS on NixOS with this, please try them out and report back (👍/👎, logs, edge cases) on the relevant issue, or open a new one. Real-world reports are the fastest way to move this from "experimental" to "trusted."
Warning
This is an experimental, unofficial port. It is not supported by Proxmox Server Solutions GmbH. Do not contact Proxmox support for issues with this packaging. The web UI carries a banner saying as much. See Status & limitations before relying on it for anything you care about.
- NixOS VM based Tests
- Real World Testing
- Gather Feedback
- Wait on The Proxmox Trademark Situation
- Upstream to Nixpkgs
pkgs.proxmox-backup-server: the base build (binariesproxmox-backup-api,proxmox-backup-proxy,proxmox-backup-manager,proxmox-tape,pmt,pmtx, the debug/migration helpers, the bundled web UI, and shell completions). See How it works for why this isn't run directly.pkgs.proxmox-backup-server-fhs: the runnable package, the base wrapped in an FHS environment. This is what the module and the CLIs use.services.proxmox-backup-server: a NixOS module that creates theproxmox-backup-serveruser/group, the runtime/state/cache/log directory tree, PAM auth integration, and theproxmox-backup/proxmox-backup-proxysystemd units plus the daily-update timer.
- Nix with flakes enabled.
- A
x86_64-linuxoraarch64-linuxhost. The bundled web UI.debassets areamd64/all, and PBS itself is Linux-only.
Prebuilt packages are pushed to Cachix from GitHub Actions. To use the cache, add this to your Nix configuration:
{
nix.settings = {
substituters = [ "https://awildleon-nixos-pbs.cachix.org" ];
trusted-public-keys = [
"awildleon-nixos-pbs.cachix.org-1:4kEEBSONGJ0F7Ita/3ZRcTWaR6M7YHXhltJaoEYl3ew="
];
};
}Or for a single command:
nix build .#proxmox-backup-server-fhs \
--extra-substituters https://awildleon-nixos-pbs.cachix.org \
--extra-trusted-public-keys awildleon-nixos-pbs.cachix.org-1:4kEEBSONGJ0F7Ita/3ZRcTWaR6M7YHXhltJaoEYl3ew=nix build .#proxmox-backup-server-fhs # runnable, FHS-wrapped
nix build .#proxmox-backup-server # base build onlyThe -fhs result contains the FHS-wrapped CLI launchers under bin/ and the daemon launchers
under libexec/proxmox-backup/. The base result holds the raw binaries and assets in a normal
Nix layout (bin/, lib/<multiarch>/proxmox-backup/, share/).
The flake ships a throwaway pbs-test-vm NixOS configuration so you can poke at a running
instance without touching your host.
Build and run it:
./scripts/run-vm.shThis builds the VM and launches it with all runtime state pinned to ./.vm-state/
(gitignored), so the system disk and the secondary datastore disk (/dev/vdb, a
blank 20 GiB image) persist across reboots. Delete .vm-state/ to start fresh.
Running the generated
run-pbs-test-vmscript directly also works, but it stores the secondary disk in a throwaway/tmp/nix-vm.*dir that is recreated blank on every boot — usescripts/run-vm.shif you want the disk to stick around.For an auto-rebuilding clean-slate dev loop, use
./scripts/dev-loop.sh.
Then access:
| Service | Address |
|---|---|
| PBS web / API | https://localhost:8007/ (self-signed cert) |
| SSH | ssh -p 2222 root@localhost |
The VM's root password is nixos. Log in to the web UI with user root@pam and that same
password.
Add the flake as an input and import the module:
{
inputs.pbs.url = "github:AWildLeon/nixos-pbs"; # or path:/... for local work
outputs = { self, nixpkgs, pbs, ... }: {
nixosConfigurations.host = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
pbs.nixosModules.proxmox-backup-server
({ pkgs, ... }: {
nixpkgs.overlays = [ pbs.overlays.default ];
services.proxmox-backup-server = {
enable = true;
openFirewall = true;
};
})
];
};
};
}The repo ships a default.nix / shell.nix that re-export the same outputs via
NixOS/flake-compat, so you don't need flakes
enabled. Pin the repo with fetchTarball (or a channel / niv / a vendored checkout) and
use the overlays.default and nixosModules.proxmox-backup-server attributes in your
configuration.nix:
{ pkgs, ... }:
let
pbs = import (fetchTarball {
url = "https://github.com/AWildLeon/nixos-pbs/archive/main.tar.gz";
# sha256 = lib.fakeSha256; # pin this for reproducibility
});
in
{
imports = [ pbs.nixosModules.proxmox-backup-server ];
nixpkgs.overlays = [ pbs.overlays.default ];
services.proxmox-backup-server = {
enable = true;
openFirewall = true;
};
}To just build the package without flakes: nix-build (produces ./result).
After a rebuild, set things up from the CLI (the web login uses PAM, so root@pam with the
host's root password works too):
proxmox-backup-manager user list
# then create a datastore, e.g. (or do it via the UI)
proxmox-backup-manager datastore create main /var/lib/proxmox-backup/datastores/main| Option | Type | Default | Description |
|---|---|---|---|
services.proxmox-backup-server.enable |
bool | false |
Enable the Proxmox Backup Server service. |
services.proxmox-backup-server.package |
package | pkgs.proxmox-backup-server-fhs |
The PBS package to use. |
services.proxmox-backup-server.openFirewall |
bool | false |
Open TCP port 8007 for the web/API proxy. |
services.proxmox-backup-server.sslCertificate |
nullOr path | null |
PEM cert to serve; null uses PBS's self-signed cert. |
services.proxmox-backup-server.sslCertificateKey |
nullOr path | null |
PEM private key matching sslCertificate. |
services.proxmox-backup-server.ensureDatastores |
attrsOf (submodule) | { } |
Datastores to create/update on activation. |
services.proxmox-backup-server.ensurePruneJobs |
attrsOf (submodule) | { } |
Prune jobs to create/update on activation. |
services.proxmox-backup-server.ensureVerifyJobs |
attrsOf (submodule) | { } |
Verification jobs to create/update on activation. |
services.proxmox-backup-server.ensureSyncJobs |
attrsOf (submodule) | { } |
Sync jobs to create/update on activation. |
By default PBS serves a self-signed certificate it generates on first start. To use your own, point both options at PEM files:
services.proxmox-backup-server = {
sslCertificate = "/run/secrets/pbs/cert.pem"; # cert (chain)
sslCertificateKey = config.age.secrets.pbsKey.path; # matching private key
};The files are copied (not symlinked) into /etc/proxmox-backup/proxy.{pem,key}
before the daemons start, so PBS uses them instead of generating a self-signed
cert; unset either option and it falls back to the self-signed one. Keep the key
out of the world-readable Nix store — reference a path from your secrets tooling
(agenix/sops-nix). When the source files change, the daemons restart and re-copy
them (so a renewed cert is picked up on the next nixos-rebuild switch).
Datastores and prune/verify/sync jobs can be declared in Nix instead of clicking
through the UI. On activation a proxmox-backup-setup oneshot service reconciles
them through proxmox-backup-manager: each declared resource is created if
missing and updated to match otherwise. Anything you create in the GUI but do
not declare here is left untouched (no deletion).
services.proxmox-backup-server = {
enable = true;
openFirewall = true;
ensureDatastores.main = {
path = "/mnt/backup/main"; # parent must exist (e.g. a `fileSystems` mount)
comment = "Primary store";
gcSchedule = "daily";
};
ensurePruneJobs.main-prune = {
datastore = "main";
schedule = "daily";
settings = { keep-daily = 7; keep-weekly = 4; };
};
ensureVerifyJobs.main-verify = {
datastore = "main";
schedule = "sun 02:00";
};
};Each submodule exposes a few first-class options (path/datastore,
comment, gcSchedule, schedule, disable) plus a freeform settings
attrset for any other PBS flag — keys use the kebab-case spelling PBS expects
(e.g. keep-daily) and map to --<key> <value>. The datastore path is the
host path; remember a datastore on the bare root / needs the /hostsys prefix
(see Datastore paths). Remotes are not managed yet, so a
remote-backed sync job needs its remote created in the GUI first.
PBS is built as a normal rustPlatform.buildRustPackage derivation from the upstream git
repos (proxmox-backup, proxmox, proxmox-fuse, pxar, pathpatterns), with a small
cargo patch to re-route dependencies that aren't on crates.io. The web UI is assembled in
postPatch/postInstall: the official .deb assets are unpacked, the ExtJS bundle is
concatenated from the upstream Makefile's file list, and UI features that don't apply to a
NixOS host (xterm.js shell, APT updates/repositories, disk/ZFS management, network/time
config, reboot/shutdown buttons) are trimmed out.
PBS hardcodes Debian-style FHS paths at runtime (/usr/share/javascript/proxmox-backup,
/usr/lib/<multiarch>/proxmox-backup, /usr/bin/ip, ...), so packaging is split in two, the
way nixpkgs handles FHS-assuming software (compare steam / steam-run):
proxmox-backup-serveris the base build. It lays the binaries and web assets out in a normal Nix layout ($out/{bin,lib,share}); it is not run directly.proxmox-backup-server-fhswraps each binary in abuildFHSEnv.buildFHSEnvmaps the base package's$out/{bin,lib,share}onto/usr, giving PBS the paths it expects, bind-mounts/runand/varfrom the host, and provides a private/etcthat already links the host'spasswd/group/shadow/pam.d/ssl(so PAM and TLS work); the writable config dir/etc/proxmox-backupis bound in on top.
The systemd units and the CLIs on $PATH all come from proxmox-backup-server-fhs, so the
NixOS module carries no bind-mount plumbing of its own.
The daemons run inside the FHS sandbox, but buildFHSEnv automatically bind-mounts every
top-level host directory into the sandbox at the same path (recursively, so submounts like a
ZFS pool or a dedicated disk come along). So datastores on the usual locations work out of
the box at their normal host paths — no prefix or extra config needed:
/var/...(e.g. the default/var/lib/proxmox-backup/datastores/...)/mnt/...,/media/...,/srv/...,/opt/.../home/...,/root/...,/boot/...,/tmp/...- any custom top-level mount, e.g. a ZFS pool at
/tank/...or a disk mounted at/data/...
The only top-level names that are not bridged are the ones the sandbox owns or ignores:
/usr, /bin, /sbin, /lib, /lib32, /lib64, /libexec, /nix, /dev, /proc, and
/etc — you would not site a datastore in any of those anyway.
The one path that does not work is the bare filesystem root /: the sandbox / is a
throwaway namespace root, so a datastore created directly at / is written there and
silently vanishes on the next request. For that case the wrapper also binds the real host
root in at /hostsys, so you can address the top of the host filesystem explicitly:
proxmox-backup-manager datastore create root /hostsys/subdirNote
Binds are set up when the daemons start, so a filesystem mounted after the PBS services
are already running won't be visible until you restart them. Declarative fileSystems
mounts (mounted at boot, before PBS starts) are fine.
Warning
/hostsys exposes the entire host filesystem (read-write) to the PBS daemons, and
proxmox-backup-api runs as root.
flake.nix # packages, overlay, NixOS module, test VM
default.nix / shell.nix # non-flake entry points (via flake-compat)
overlay.nix # nixpkgs overlay, shared by both entry points
modules/proxmox-backup-server.nix # the services.proxmox-backup-server module
pkgs/proxmox-backup-server/
package.nix # base build (orchestration only)
sources.nix # pinned upstream git + .deb sources
www/ # web UI replacement files + transform scripts
Cargo.lock # pinned Rust dependency lock
0001-cargo-re-route-...patch # cargo source re-routing patch
pkgs/proxmox-backup-server-fhs/
package.nix # buildFHSEnv wrapper around the base build
The package builds and the core services run, but the following still need testing or are known to be incomplete. Each has a tracking issue. Please add your results there:
- Runtime service behavior under real backup/restore/sync/GC workloads.
- Generated docs and manpages.
- PAM / authentication integration beyond basic
root@pamlogin. - Tape backup is untested and likely does not work. The tape tooling
(
proxmox-tape,pmt,pmtx,sg-tape-cmd) is built and the UI is left in place, but it has not been exercised on this port and is not expected to work as-is. If you need tape, please open an issue so it can be looked at. - Assumptions that only hold on Debian-style systems.
Contributions and bug reports are welcome.
The packaging in this repository (the Nix expressions, module, and docs) is licensed under
the MIT license. See LICENSE.
Note that the software it builds, Proxmox Backup Server, remains licensed under the AGPL-3.0-only by its authors; the MIT license here does not relicense it. Proxmox® is a registered trademark of Proxmox Server Solutions GmbH. This is an independent, unofficial packaging effort and is not affiliated with or endorsed by Proxmox.
Note
Phrasing borrowed from nixpkgs: the MIT license does not apply to the packages built by this repo, merely to the files in this repository (the Nix expressions, build scripts, NixOS module, etc.). It also might not apply to patches included here, which may be derivative works of the packages to which they apply. Those artifacts are covered by the licenses of the respective packages.
The end goal of this project is to get a stable Proxmox Backup Server (with module) upstreamed into NixOS/nixpkgs.
Large parts of this were written with Claude Code. I've reviewed everything, corrected where needed, and understand every line of it. That said: there may be dragons.
