Skip to content

Repository files navigation

Proxmox Post-Installation Toolkit

License Proxmox

Idempotent scripts for Proxmox VE post-installation configuration. Brought to you by the hoopy froods at HyperSec.


What's in the Box

  • System Optimisation - Kernel tuning, nested virtualisation, IOMMU/VFIO passthrough, SSD TRIM
  • Network Tuning - Tier-based TCP/UDP buffers (1/10/25/40/100/200 GbE), BBR congestion control, NIC offloading
  • Power Management - CPU governors, PCIe ASPM, thermal monitoring, power profiles
  • ZFS Tuning - RAM-aware ARC sizing, autotrim, dataset settings
  • Repository Config - No-subscription repos, enterprise repo disable
  • Conservative Updates - N-1 minor version pinning, UI customisation, APT persistence hooks
  • Internal NAT - Host-side VM networking with any IPv4 CIDR
  • Remote Access - AWS SSM and NetBird agents for emergency console access
  • Drive Error Recovery - SCT ERC and kernel timeouts so ZFS sees errors, not stalls
  • Artefact Cleanup - Removes superseded config left by earlier runs

Goal

Make a Proxmox VE host on the community (no-subscription) repositories behave as close to an enterprise deployment as it can: current on fixes, never first onto a new minor release, tuned for its hardware, and honest about what it is (the subscription nags are replaced with a statement of the actual update policy, not just hidden).

Two deployment targets, one deployment

Target 1 Target 2
Shape Single node Multi-node cluster
Who Solo dev, small team, home brick server Production private cloud
Scripts run All of them All of them, on every node

The scripts are the same on both, deliberately. Work that starts on someone's home Proxmox box should move to a production cluster without a rebuild, so nothing here is conditional on being "the small one".

Only two things differ, and both are detected rather than configured:

  • Update policy is coordinated cluster-wide. A cluster requires matching versions across nodes, so the resolved series are written to /etc/pve (the replicated cluster filesystem) and every node reproduces the same decision instead of resolving independently and drifting apart. A standalone node has no cluster filesystem and simply resolves locally.
  • Bridge netfilter is only asserted where pve-firewall is enabled.

Quick Start

# Download and extract
wget https://github.com/hyperi-io/proxmox/archive/refs/heads/main.zip
unzip main.zip && cd proxmox-main/postinstall
chmod +x *.sh *.py

# Run in order
sudo ./proxmox-cleanup.sh status    # What did an earlier run leave behind?
sudo ./proxmox-repo.sh              # Configure repositories
sudo ./proxmox-optimize.sh          # Core system optimisation
sudo ./proxmox-zfs.sh               # ZFS tuning (if applicable)
sudo ./proxmox-disk-errors.sh apply # Drive error recovery (if ZFS)
sudo ./proxmox-power-management.sh  # Power management (optional)
sudo ./proxmox-network.sh 10gbe     # Network tuning (optional)
sudo ./proxmox-update-policy.sh enable  # Conservative updates (optional)

Run proxmox-cleanup.sh status first on any host that has run an earlier version of these scripts -- it reports superseded config that is still active and changes nothing until you run apply.

After running, update GRUB and reboot if prompted:

sudo update-grub && sudo reboot

Scripts

proxmox-repo.sh

Configures Proxmox VE repositories for community (no-subscription) use.

What it does:

  • Creates no-subscription repository configuration
  • Disables enterprise repositories
  • Updates package lists

Note: UI customisations (warning suppression) are handled by proxmox-update-policy.sh.

Property Value
Idempotent Yes
Reboot No
Backup None (safe operations)

proxmox-update-policy.sh

Conservative update policy with n-0.1 minor version pinning. Keeps you one minor version behind bleeding edge while allowing patch updates.

What it does:

  • Pins Proxmox packages to one minor version behind latest
  • Applies UI patches to replace "not recommended for production" warnings
  • Creates APT hook for persistence across package updates
  • Supports daily cron job for automatic policy refresh
  • Never downgrades below installed version

Policy behaviour:

  • MAJOR: Same as latest available
  • MINOR: max(installed, n-0.1) - never downgrades
  • PATCH: Latest within target minor

Example: If repo has 9.2.3, policy pins to 9.1.* (gets 9.1.x patches, skips 9.2.x)

Commands:

sudo ./proxmox-update-policy.sh enable       # Pin + suppress warnings
sudo ./proxmox-update-policy.sh enable --no-ui  # Pin only, keep warnings
sudo ./proxmox-update-policy.sh ui-only      # Suppress warnings, pin nothing
sudo ./proxmox-update-policy.sh ui-disable   # Restore warnings, keep pinning
sudo ./proxmox-update-policy.sh disable      # Remove both
sudo ./proxmox-update-policy.sh status       # Show policy and versions
sudo ./proxmox-update-policy.sh update       # Refresh pinning
sudo ./proxmox-update-policy.sh cron-enable  # Install daily cron
sudo ./proxmox-update-policy.sh cron-disable # Remove cron

Pinning and the UI customisation are independent. A host can hold packages back, suppress the subscription warnings, both, or neither:

Want Command
Both enable
Pin only, keep the warnings enable --no-ui
Warnings gone, track latest ui-only
Neither disable

ui-only is the right choice for a host that should stay current -- a lab or a personal box -- but without Proxmox telling you off about it on every login.

UI customisations:

When enabled, the web interface reports the update policy that is actually in force instead of a generic warning:

  • Suppresses the "no valid subscription" modal shown after login
  • Replaces the no-subscription repository warning with "Conservative update policy active", and only when that warning was the only thing wrong -- a genuine problem still surfaces
  • Persists across package updates via an APT hook

This relabels repository status. It does not represent the host as holding a subscription and does not change what the host is entitled to.

How it is implemented, and why it matters:

The customisation is a single injected JavaScript file using ExtJS class overrides -- the framework's own extension mechanism. No Proxmox source file is modified. The only edit is one <script> tag added to index.html.tpl.

Earlier versions text-patched proxmoxlib.js in seven places. That approach broke on every PVE release that restructured the file (on PVE 9.2.5 only two of the seven still matched), and because the patches carried placeholder line numbers, patch(1) fuzzy-matched short repeated lines and could edit an unintended one. The override survives package updates untouched, and if upstream renames what it hooks the original warning simply reappears rather than the UI breaking.

Deleting /usr/share/pve-manager/js/conservative-policy.js restores stock behaviour completely.

Compatibility: Tested on PVE 9.x only. PVE 8.x may work.

Property Value
Idempotent Yes
Reboot No
Backup /root/backup/proxmox-config/

proxmox-optimize.sh

Core system configuration for Proxmox VE hosts.

What it does:

  • Backs up current settings
  • Installs monitoring tools (htop, iotop, smartmontools)
  • Configures kernel parameters (sysctl)
  • Enables nested virtualisation (Intel VT-x / AMD-V)
  • Configures IOMMU for device passthrough
  • Enables SSD TRIM
  • Creates management scripts

Kernel parameters applied:

vm.swappiness=10
vm.vfs_cache_pressure=50
net.core.netdev_max_backlog=8192
net.ipv4.tcp_fin_timeout=30
fs.file-max=2097152
net.bridge.bridge-nf-call-iptables=1

Created commands: proxmox-status

Property Value
Idempotent Yes
Reboot Yes (for IOMMU/nested virt)
Backup /root/backup/proxmox-config/

proxmox-network.sh

Network configuration based on interface speed tier.

Usage:

sudo ./proxmox-network.sh 1gbe     # 1 Gigabit (conservative)
sudo ./proxmox-network.sh 10gbe    # 10 Gigabit (recommended)
sudo ./proxmox-network.sh 25gbe    # 25 Gigabit
sudo ./proxmox-network.sh 40gbe    # 40 Gigabit
sudo ./proxmox-network.sh 100gbe   # 100 Gigabit
sudo ./proxmox-network.sh 200gbe   # 200 Gigabit

What it does:

  • Detects or accepts network speed tier
  • Configures TCP/UDP buffer sizes
  • Configures queue depths and backlogs
  • Enables BBR congestion control for 10GbE+
  • Configures NIC ring buffers and hardware offloading
  • Supports jumbo frames (--jumbo flag)

Tier optimisations:

Tier TCP Buffer Max Backlog Congestion Ring Buffer
1 GbE 8 MB 5K CUBIC 512
10 GbE 32 MB 30K BBR 2048
25 GbE 64 MB 50K BBR 4096
40 GbE 128 MB 100K BBR 8192
100 GbE 256 MB 250K BBR 8192
200 GbE 512 MB 500K BBR 8192

Created commands: network-status, network-test

Property Value
Idempotent Yes
Reboot No
Backup /root/backup/proxmox-config/

proxmox-power-management.sh

Power management and thermal control.

What it does:

  • Configures CPU frequency governor (schedutil)
  • Applies vendor-specific settings (Intel/AMD)
  • Enables PCIe ASPM (powersave mode)
  • Configures SATA link power management
  • Enables network power management (WoL, EEE)
  • Configures USB selective suspend
  • Enables PCI runtime power management
  • Updates kernel boot parameters

Kernel parameters (Intel):

intel_idle.max_cstate=6
intel_pstate=passive
pcie_aspm=powersave

Kernel parameters (AMD):

processor.max_cstate=6
amd_pstate=passive
pcie_aspm=powersave

Created commands: power-status, thermal-check, performance-mode, balanced-mode, powersave-mode

Systemd service: proxmox-power.service (auto-applies on boot)

Property Value
Idempotent Yes
Reboot Yes (for kernel parameters)
Backup /root/backup/proxmox-config/

proxmox-zfs.sh

Safe ZFS optimisation for Proxmox storage.

What it does:

  • Calculates ARC size based on total RAM
  • Applies runtime ARC limits
  • Creates persistent ZFS module configuration
  • Enables autotrim on all pools
  • Optimises VM storage datasets (atime, xattr)
  • Generates status and tuning scripts

ARC sizing:

Total RAM ARC Min ARC Max VM Reserve
16 GB 1 GB 2 GB 14+ GB
32 GB 1 GB 3 GB 29+ GB
64 GB 2 GB 4 GB 60+ GB
128 GB 2 GB 6 GB 122+ GB
256+ GB 3 GB 8 GB 248+ GB

Safety settings preserved: sync=standard, compression (Proxmox-managed), primarycache=all

zvol taskq sizing: zvol_threads scales with CPU count and zvol_num_taskqs partitions zvols across independent taskqs. A single shared taskq is a host-wide single point of failure: if one device wedges, its blocked threads starve guests on completely unrelated, healthy pools.

Created commands: zfs-status, zfs-tune-guide

Property Value
Idempotent Yes
Reboot Recommended
Backup None (safe operations)

proxmox-disk-errors.sh

Bounds how long a drive may retry a bad sector, so it returns an error instead of stalling.

Why this matters more than it sounds: ZFS can only act on an error, never on a stall. A drive that retries without bound never triggers the repair-from-redundancy path, so a bad block is never healed and every I/O queued behind it waits. Redundancy you cannot reach is not redundancy.

What it does:

  • Enables SCT ERC (7s) on drives that support it -- most do, and most ship with it Disabled
  • Falls back to the kernel command timeout (10s, vs the 30s default) for drives with no SCT support
  • Reapplies SCT ERC on every boot via systemd, because it does not survive a power cycle
  • Matches the udev rule on drive serial, not sdX, which is not stable

Only applied to pools that HAVE redundancy. On a single disk, telling the drive to give up early tells it to abandon data nothing else holds. The script detects layout and skips non-redundant pools.

sudo ./proxmox-disk-errors.sh apply    # Configure
sudo ./proxmox-disk-errors.sh status   # Per-drive report
sudo ./proxmox-disk-errors.sh remove   # Revert
Property Value
Idempotent Yes
Reboot No
Backup N/A

proxmox-cleanup.sh

Finds and removes artefacts left by earlier versions of these scripts.

None of the other scripts clean up their predecessors. Because they write into drop-in directories that apply everything they contain, a superseded generation does not become inert -- it stays active and fights the current one.

What it looks for:

  • APT hooks from earlier generations still patching the same files
  • sysctl drop-ins superseded by 98-proxmox-optimize.conf
  • sysctl files that are raw sysctl -a dumps rather than configs
  • sysctl files setting read-only kernel statistics, which error on every boot
  • More than one network tier file, all applying at once
sudo ./proxmox-cleanup.sh status   # Report only, changes nothing (default)
sudo ./proxmox-cleanup.sh apply    # Remove, backing each up first
Property Value
Idempotent Yes
Reboot No
Backup /root/backup/proxmox-config/removed-<timestamp>/

proxmox-internal-nat.sh

Host-side internal VM network with NAT outbound.

Usage:

sudo ./proxmox-internal-nat.sh apply --lan-cidr 10.42.0.0/16
sudo ./proxmox-internal-nat.sh remove
sudo ./proxmox-internal-nat.sh status --lan-cidr 10.42.0.0/16
sudo ./proxmox-internal-nat.sh health --lan-cidr 10.42.0.0/16

Commands:

  • apply - Create internal bridge, enable forwarding, add NAT rules
  • remove - Remove all configuration and rules
  • status - Show CONFIG intent vs LIVE runtime state
  • health - Deeper dataplane checks (routes + nft rules/counters)

Options:

  • --lan-cidr <CIDR> - Network CIDR (required for apply/status/health)
  • --lan-gw <IP> - Gateway IP (default: first usable host)
  • --wan-bridge <name> - WAN bridge name (default: vmbr0)
  • --lan-bridge <name> - LAN bridge name (default: vmbr1)
  • --reload - Reload networking after changes

Safety:

  • Does NOT reload networking unless --reload is passed
  • Uses isolated nftables tables (won't conflict with pve-firewall)
  • All files backed up before modification
Property Value
Idempotent Yes
Reboot No
Backup Inline (marked sections)

proxmox-ssm.py

AWS Systems Manager agent for emergency console access via AWS SSM Session Manager.

Usage:

# Install with auto-created IAM role and activation
sudo ./proxmox-ssm.py install --region ap-southeast-2

# Install with existing activation
sudo ./proxmox-ssm.py install --activation-code XXXX --activation-id YYYY --region ap-southeast-2

# Check status
sudo ./proxmox-ssm.py status

# Uninstall
sudo ./proxmox-ssm.py uninstall

What it does:

  • Installs AWS SSM agent from official .deb package
  • Creates IAM role with SSM trust policy (if not exists)
  • Creates hybrid activation for on-premises registration
  • Registers host as AWS managed instance
  • Enables Session Manager access via AWS console/CLI

Region auto-detection (in order):

  1. --region flag
  2. AWS_REGION environment variable
  3. AWS_DEFAULT_REGION environment variable
  4. aws configure get region
  5. Error if none found

Requirements:

  • AWS CLI installed with appropriate IAM permissions
  • Internet connectivity to AWS SSM endpoints
  • For Session Manager: Advanced-instances tier enabled

Connect after install:

aws ssm start-session --target mi-XXXXXXXXX --region ap-southeast-2
Property Value
Idempotent Yes
Reboot No
Backup /root/backup/proxmox-config/

proxmox-netbird.py

NetBird WireGuard mesh agent for emergency access. BSD-3-Clause licensed open source.

Usage:

# Install and connect to NetBird cloud
sudo ./proxmox-netbird.py install --setup-key nb-setup-XXXXXXXX

# Install and connect to self-hosted control plane
sudo ./proxmox-netbird.py install --setup-key XXXX --management-url https://netbird.example.com:443

# Check status
sudo ./proxmox-netbird.py status

# Uninstall
sudo ./proxmox-netbird.py uninstall

What it does:

  • Adds NetBird APT repository
  • Installs netbird package
  • Connects to management server with setup key
  • Creates WireGuard tunnel (wt0 interface)

Getting a setup key:

  1. Go to https://app.netbird.io (or your self-hosted console)
  2. Navigate to Setup Keys
  3. Create a new key (reusable or one-time)
  4. Use with --setup-key

Supports:

  • NetBird cloud (api.netbird.io) - default
  • Self-hosted control plane (--management-url)
Property Value
Idempotent Yes
Reboot No
Backup /root/backup/proxmox-config/

Configuration Files

Created/Modified

/etc/sysctl.d/98-proxmox-optimize.conf       # Kernel parameters (base)
/etc/sysctl.d/99-proxmox-network-<tier>.conf # Kernel parameters (network tier)
/etc/modprobe.d/kvm-nested.conf              # Nested virtualisation
/etc/modprobe.d/zfs.conf                     # ZFS ARC + zvol taskq
/etc/modules                                 # VFIO modules
/etc/default/grub                            # Boot parameters
/etc/default/cpufrequtils                    # CPU governor
/etc/systemd/system/proxmox-power.service    # Power service
/etc/systemd/system/zfs-disk-erc.service     # SCT ERC (reapplied each boot)
/etc/udev/rules.d/60-zfs-disk-timeouts.rules # Kernel timeout, no-SCT drives
/etc/apt/sources.list.d/debian-official.sources   # Debian repos
/etc/apt/sources.list.d/proxmox.sources      # Proxmox no-subscription repo
/etc/apt/preferences.d/proxmox-conservative  # Update policy pinning
/etc/apt/apt.conf.d/99proxmoxpolicy          # Update policy APT hook
/etc/pve/proxmox-conservative-series         # Cluster-agreed pin series
/usr/local/bin/proxmox-policy-hook.sh        # Update policy hook script
/usr/share/pve-manager/js/conservative-policy.js  # UI policy flag

The sysctl files are numbered so the network tier file deliberately overrides the base one where they overlap. Only ONE tier file should ever exist -- see proxmox-cleanup.sh if more than one is present.

Backup Locations

All backups are stored in /root/backup/proxmox-config/ with timestamped filenames:

/root/backup/proxmox-config/
  - sysctl-backup-YYYYMMDD_HHMMSS.conf      # System
  - grub.backup.YYYYMMDD_HHMMSS             # System
  - network-sysctl-YYYYMMDD_HHMMSS.conf     # Network
  - power-grub-YYYYMMDD_HHMMSS              # Power
  - power-cpufrequtils-YYYYMMDD_HHMMSS      # Power
  - proxmoxlib.js.original                  # Update policy
  - pvemanagerlib.js.original               # Update policy
  - index.html.tpl.original                 # Update policy

Management Commands

After installation, these commands are available:

# System
proxmox-status              # Overall system status

# Network
network-status              # Network configuration
network-test                # Performance testing guide

# Power
power-status                # Power configuration
thermal-check               # CPU temperature check
performance-mode            # Switch to performance
balanced-mode               # Switch to balanced
powersave-mode              # Switch to powersave

# ZFS
zfs-status                  # ZFS status overview
zfs-tune-guide              # Tuning recommendations

# Update Policy
proxmox-update-policy.sh status   # Policy status
proxmox-update-policy.sh enable   # Enable policy
proxmox-update-policy.sh disable  # Disable policy
proxmox-update-policy.sh update   # Refresh pinning

# Remote Access
proxmox-ssm.py status             # SSM agent status
proxmox-netbird.py status         # NetBird status

TRIM and thin zvols

mkfs.ext4 issues a full-device TRIM by default. Against a thin zvol that discard is enormous, and ZFS has to digest all of it before anything else proceeds -- formatting a 2 TB thin zvol produced a 769-second transaction group during the incident that prompted proxmox-disk-errors.sh, blocking every guest on the host.

Rules:

  • Always pass -E nodiscard when running mkfs.ext4 on a zvol.

    mkfs.ext4 -E nodiscard /dev/zvol/vmdata/vm-100-disk-1
  • Reclaim deliberately, not continuously. proxmox-optimize.sh enables the weekly fstrim.timer, which is correct for ordinary filesystems but will repeat a large discard against mostly-empty thin zvols. For bulk data volumes, add X-fstrim.notrim to the fstab options and run a single fstrim <mountpoint> by hand while the host is idle. Batched discard is far cheaper than synchronous per-delete discard.

  • Avoid fstrim -a on guests holding bulk thin volumes: it ignores the X-fstrim.notrim exclusion.


Testing

The test suite runs on any machine with bash and apt. No Proxmox, no root, no network:

./tests/test-update-policy.sh   # the n-0.1 rule, version arithmetic
./tests/test-disk-errors.sh     # pool redundancy + SCT ERC detection
./tests/test-cleanup.sh         # superseded-artefact classification
./tests/test-apt-pinning.sh     # apt behaviour against a throwaway repo

The scripts under test are sourceable: sourcing defines their functions and returns without executing anything, so the decision logic can be driven directly.

test-apt-pinning.sh is the one that matters most. It builds a real apt repository of throwaway packages at known versions, points an isolated apt root at it, and asserts on apt-cache policy -- so "the pin actually holds" is proven rather than assumed. The previous pinning implementation looked correct and pinned almost nothing; only asserting against apt catches that.

Integration coverage that needs a real PVE install (UI patching, the APT hook, ZFS module parameters) is documented in tests/nested-pve.md.


Safety

Idempotent Design

All scripts are safe to run multiple times:

  • Check current state before applying changes
  • Skip already-configured settings
  • Clear status messages (Already configured vs Newly configured)

Data Safety

  • No data loss risk - all optimisations preserve data integrity
  • Automatic backups - system configs backed up before changes
  • Conservative defaults - reliability over performance
  • Proxmox-aware - respects Proxmox's management of VMs and storage

Error Handling

  • Error trapping (set -e, trap)
  • Graceful degradation on non-critical failures
  • Detailed error messages with line numbers

CPU Support

Intel

  • Intel VT-x nested virtualisation
  • Intel IOMMU (VT-d)
  • Intel P-state driver
  • Intel Turbo Boost control

AMD

  • AMD-V nested virtualisation
  • AMD IOMMU (AMD-Vi)
  • AMD P-state driver (EPP mode)
  • AMD Core Performance Boost

Compatibility

Tested on:

  • Proxmox VE 9.x / Debian 13 (Trixie) only
  • PVE 8.x may work but is untested
  • Intel Xeon, Core i-series CPUs
  • AMD EPYC, Ryzen CPUs

Requirements:

  • x86_64 CPU with virtualisation extensions (Intel VT-x / AMD-V)
  • IOMMU support (Intel VT-d / AMD-Vi) for device passthrough
  • lm-sensors compatible CPU for thermal monitoring

Troubleshooting

Script Won't Run

sudo -i                    # Ensure root
chmod +x /path/to/script.sh
pveversion                 # Check Proxmox version

IOMMU Not Enabled

grep GRUB_CMDLINE_LINUX_DEFAULT /etc/default/grub
update-grub && reboot
dmesg | grep -i iommu      # Verify after reboot

Power Management Not Working

ls /sys/devices/system/cpu/cpu0/cpufreq/
modprobe acpi-cpufreq      # or amd-pstate / intel_pstate
systemctl status proxmox-power.service

ZFS Script Fails

zpool list                 # Verify ZFS installed
whoami                     # Check root
lsmod | grep zfs           # Verify modules loaded

Temperature Sensors Not Working

apt-get install lm-sensors
sensors-detect --auto
sensors

Versioning

This project uses Semantic Versioning:

  • MAJOR - Incompatible changes
  • MINOR - Backwards-compatible additions
  • PATCH - Backwards-compatible fixes

See CHANGELOG.md for history.


Contributing

Contributions welcome. See CONTRIBUTING.md for details.

Quick checklist:

  • Scripts remain idempotent
  • Follow existing error handling patterns
  • Test on Proxmox VE 9.x
  • Update documentation
  • Pass ShellCheck

License

Apache License 2.0 - see LICENSE.

Copyright 2025 HyperSec

Licensed under the Apache License, Version 2.0

Relationship to Proxmox VE

Apache-2.0 covers this toolkit. It does not cover Proxmox VE.

Proxmox VE and proxmox-widget-toolkit are Copyright Proxmox Server Solutions GmbH and licensed AGPLv3. Nothing here vendors, redistributes or relicenses any part of them.

These scripts configure a Proxmox installation on the machine they are run on. The UI customisation is an ExtJS override -- it calls public framework and Proxmox.Utils entry points and adds its own class; it contains no Proxmox source. The only change made to a Proxmox-owned file is a single <script> tag in index.html.tpl, applied locally at install time and reversible with proxmox-update-policy.sh disable.


Disclaimer

These scripts modify system configuration. While designed to be safe and idempotent:

  • Test in non-production first
  • Review code before running on production
  • Ensure you have backups

Use at your own risk.


Support

  • Issues: GitHub Issues
  • Documentation: This README and inline script comments

Acknowledgments

  • Proxmox VE Team
  • Debian Project
  • Community contributors

About

Idempotent shell scripts for Proxmox VE post-installation configuration

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages