Proxmox VE 9.1 - Configuration
As previously stated, I have been reading a lot of Brandon Lee and realized that I have been configuring my LXC's and Proxmox server with obsolete iptables.
This has resulted in a complete rewrite of the associated configuration scripts
Brandon Lee is located here



The following changes were made
- Previously rebuilt the server to a clean install of Proxmox VE 9.1
- Rebuild all the LXC Debian 13 containers based on Proxmox Community Scripts
- Review networking especially the sysctl commands
- Implemented a lot of Brandon Lees code and ideas - he is very good.
- Move all firewalls to nftables and ditch iptables - you can't on the Proxmox server
- Test, test and test.
At time of writing and multiple reboots I have not found a problem with the server, the reverse proxy LXC or the Podman application servers since I have ditched Docker due to the November fiasco that created havoc for most enthusiasts.
Network Configuration
The following basically gives you an idea of the network
- All IPs are issued by the router - ipv4 and ipv6 - some are fixed
- The Proxmox server hosts all applications whether they are local or WAN enabled
- The Proxmox server runs LXC's due to dated hardware, has GPU enabled via LXC pass through which is very different from VM pass through
- I run a dedicated proxy on one LXC
- I then run one primary application LXC and a couple of developmental types
Proxmox Server Code
Find below the primary installation script
- Additional scripts are - gpu, postfix, fail2ban
- I won't share these as they are a work in progress and not needed to get running
#!/bin/bash
#!/bin/bash
# ========================================================================
# Proxmox VE 9.1 (Debian 13.2) PXE / Utility Node Setup Script
# ========================================================================
# This script performs a full initial configuration of a Proxmox host used
# as a PXE server, utility node, or general-purpose hypervisor in a home lab
# or small office environment.
#
# Major functions performed:
# • Rewrites /etc/network/interfaces with a clean vmbr0 bridge configuration
# • Enables IPv4/IPv6 forwarding and applies sysctl hardening
# • Installs and configures dhcpcd for stable IPv6 RA handling
# • Regenerates SSH host keys and applies a hardened sshd_config
# • Installs required packages and removes unsafe defaults
# • Configures nftables as the authoritative firewall (Proxmox firewall disabled)
# • Deploys a hardened nftables ruleset with trusted-host access only
# • Sets up nftables logging and log rotation
# • Applies kernel, memory, and system tuning parameters
# • Performs final diagnostics for networking and SSH
#
# Notes:
# • Designed for Proxmox VE 9.1 running Debian 13.2
# • Safe for single-node or non-clustered Proxmox deployments
# • Assumes primary NIC is "eth0" and main bridge is "vmbr0"
# • Script is intended for fresh installations — it overwrites key configs
# • iptables is NOT removed (required by Proxmox), but is not used
# • nftables becomes the sole active firewall
#
# Version 9.2.0
#
# Code review date: 2025-12-30
# ========================================================================
set -euo pipefail
# ------------------------------------------------------------------------
# Logging
info() { echo -e "\033[0;32m[INFO]\033[0m $*"; }
warn() { echo -e "\033[0;33m[WARN]\033[0m $*" >&2; }
error(){ echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; }
LOGFILE="/var/log/proxmox-setup-$(date +%Y%m%d-%H%M%S).log"
exec > >(tee -a "$LOGFILE") 2>&1
BACKUP_TS="$(date +%Y%m%d-%H%M%S)"
# ------------------------------------------------------------------------
# Miscellaneous Configuration
# Timezone configuration
info "Set the timezone manually to ensure consistency..."
timedatectl set-timezone Australia/Pert
# Install necessary packages prior to network changes
info "Installing additional packages - adjust as required..."
apt-get update -qq || warn "apt-get update failed, check DNS/network"
apt-get install -y -qq rsyslog mtr git curl wget build-essential dkms || error "Package installation failed"
# Remove packages as necessary prior to network changes
info "Removing packages - adjust as required..."
for pkg in ufw inetutils-telnet; do
if dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "install ok installed"; then
apt-get purge --auto-remove -y -qq "$pkg" || warn "$pkg failed to uninstall"
fi
done
# Create backups of sysctl
cp -a /etc/sysctl.d /etc/sysctl.d.bak.${BACKUP_TS}
cp -a /etc/apt/apt.conf.d /etc/apt/apt.conf.d.bak.${BACKUP_TS}
cp -a /etc/logrotate.conf /etc/logrotate.conf.bak.${BACKUP_TS}
# Skip downloading extra APT languages
info "Configuring APT to skip downloading extra language files..."
echo 'Acquire::Languages "none";' | tee /etc/apt/apt.conf.d/99-disable-languages
# Force APT to use IPv4 to avoid potential issues
info "Configuring APT to use IPv4..."
echo 'Acquire::ForceIPv4 "true";' | tee /etc/apt/apt.conf.d/99force-ipv4
# Configure kernel panic auto-reboot
info "Configuring kernel panic auto-reboot..."
echo "kernel.panic = 10" | tee /etc/sysctl.d/99-kernelpanic.conf
echo "kernel.panic_on_oops = 1" | tee -a /etc/sysctl.d/99-kernelpanic.conf
sysctl -p /etc/sysctl.d/99-kernelpanic.conf || warn "Failed to apply kernel panic sysctl settings"
# Increase system limits
info "Configuring system limits..."
echo "fs.inotify.max_user_watches = 1048576" | tee /etc/sysctl.d/99-maxwatches.conf
echo "* soft nofile 1048576" | tee /etc/security/limits.d/99-limits.conf
sysctl --system || warn "Failed to apply sysctl settings"
# Harden bash records - survive across sessions - crashes
info "Reconfiguring bash records..."
export HISTSIZE=10000
export HISTFILESIZE=100000
shopt -s histappend
export PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND"
# ------------------------------------------------------------------------
# Define primary NIC explicitly
PRIMARY_NIC="eth0"
VM_BRIDGE="vmbr0"
# ------------------------------------------------------------------------
# 1. Backup and write /etc/network/interfaces
info "Backing up and writing /etc/network/interfaces..."
cp /etc/network/interfaces /etc/network/interfaces.bak.${BACKUP_TS} || true
tee /etc/network/interfaces >/dev/null <<EOF
# Managed by setup-pxe-script.sh (${BACKUP_TS})
auto lo
iface lo inet loopback
iface ${PRIMARY_NIC} inet manual
auto ${VM_BRIDGE}
iface ${VM_BRIDGE} inet dhcp
bridge-ports ${PRIMARY_NIC}
bridge-stp on
bridge-fd 0
dns-nameservers 192.168.1.1
dns-search braedach.com
iface ${VM_BRIDGE} inet6 auto
accept_ra 2
autoconf 1
privext 0
dns-search braedach.com
EOF
info "Interfaces file written."
# ------------------------------------------------------------------------
# 2. dhcpcd setup
info "Ensuring dhcpcd installed..."
apt-get update -y
DEBIAN_FRONTEND=noninteractive apt-get install -y dhcpcd5 || true
info "Writing /etc/dhcpcd.conf..."
tee /etc/dhcpcd.conf >/dev/null <<EOF
# Managed by setup-pxe-script.sh (${BACKUP_TS})
denyinterfaces ${PRIMARY_NIC}
interface ${VM_BRIDGE}
ipv6rs
hostname $(hostname)
clientid
# Required to forward the DNS settings to LXC containers
static domain_name_servers=192.168.1.1
static domain_name_servers=fe80::f6e2:c6ff:feee:63e3
static domain_search=braedach.com
EOF
systemctl enable dhcpcd
systemctl restart dhcpcd
# ------------------------------------------------------------------------
# 3. Network sysctl tuning
# Enable IPv4 forwarding and RA acceptance on bridge
info "Applying IPv4 sysctl tuning..."
tee /etc/sysctl.d/99-ipv4-${VM_BRIDGE}.conf >/dev/null <<EOF
net.ipv4.ip_forward=1
EOF
# Enable IPv6 forwarding and RA acceptance on bridge
info "Applying IPv6 sysctl tuning..."
tee /etc/sysctl.d/99-ipv6-${VM_BRIDGE}.conf >/dev/null <<EOF
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.default.forwarding=1
net.ipv6.conf.${VM_BRIDGE}.accept_ra=2
net.ipv6.conf.${VM_BRIDGE}.autoconf=1
EOF
sysctl --system || warn "Failed to apply IPv4/IPv6 sysctl settings"
# ------------------------------------------------------------------------
# 4. Bring up bridge
info "Bringing up ${VM_BRIDGE}..."
if ip link set "${VM_BRIDGE}" up; then
info "${VM_BRIDGE} brought up successfully."
else
warn "Failed to bring up ${VM_BRIDGE} — check interface definition."
fi
info "Restarting networking service..."
if systemctl restart networking; then
info "Networking service restarted."
else
warn "Networking restart failed — continuing, but verify manually."
fi
# ------------------------------------------------------------------------
# 5. SSH regeneration and hardening
SSHD_DIR="/etc/ssh"
SSHD_CFG="${SSHD_DIR}/sshd_config"
BACKUP_DIR="${SSHD_DIR}/backup-${BACKUP_TS}"
mkdir -p "${BACKUP_DIR}"
info "Backing up sshd_config and host keys..."
cp -a "${SSHD_CFG}" "${BACKUP_DIR}/sshd_config.bak" || true
for key in ssh_host_ed25519_key ssh_host_rsa_key; do
[[ -f "${SSHD_DIR}/${key}" ]] && mv "${SSHD_DIR}/${key}" "${BACKUP_DIR}/"
[[ -f "${SSHD_DIR}/${key}.pub" ]] && mv "${SSHD_DIR}/${key}.pub" "${BACKUP_DIR}/"
done
info "Deleting old SSH host keys..."
rm -f /etc/ssh/ssh_host_*
info "Generating new ED25519 host key..."
ssh-keygen -t ed25519 -f "${SSHD_DIR}/ssh_host_ed25519_key" -N "" -o -a 100
info "Generating new RSA-4096 host key - not used - can be deleted..."
ssh-keygen -t rsa -b 4096 -f "${SSHD_DIR}/ssh_host_rsa_key" -N "" -o -a 100
chmod 600 "${SSHD_DIR}/ssh_host_ed25519_key" "${SSHD_DIR}/ssh_host_rsa_key"
chmod 644 "${SSHD_DIR}/ssh_host_ed25519_key.pub" "${SSHD_DIR}/ssh_host_rsa_key.pub"
info "Writing hardened sshd_config..."
tee ${SSHD_CFG} >/dev/null <<EOF
# Managed by setup-pxe-script.sh (${BACKUP_TS})
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
HostbasedAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
LoginGraceTime 30
MaxAuthTries 4
MaxSessions 10
HostKey ${SSHD_DIR}/ssh_host_ed25519_key
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
UseDNS no
PrintMotd no
PrintLastLog yes
AcceptEnv LANG LC_*
# Permit root password auth only from RFC1918 and link-local IPv6
Match Address 10.0.0.0/8,192.168.0.0/16,fe80::/10
PermitRootLogin yes
PasswordAuthentication yes
AuthenticationMethods any
EOF
info "Validating sshd_config..."
if sshd -t; then
info "sshd_config syntax OK, restarting ssh..."
if systemctl restart ssh; then
info "sshd restarted successfully."
else
error "systemctl restart ssh failed — check service status."
fi
else
error "sshd_config test failed — not restarting ssh."
fi
# ------------------------------------------------------------------------
# 6. Configure firewall - nftables only (Proxmox-safe)
info "Configuring nftables firewall..."
# Ensure nftables is installed and enabled
apt-get update -y
apt-get install -y nftables
systemctl enable --now nftables
# Disable Proxmox firewall (we use nftables instead)
pve-firewall stop || true
systemctl disable pve-firewall || true
# Ensure no iptables persistence is active
systemctl disable --now netfilter-persistent 2>/dev/null || true
# Deploy nftables loader
cat <<'EOF' > /etc/nftables.conf
#!/usr/sbin/nft -f
include "/etc/nftables-host.conf"
EOF
# Deploy nftables ruleset
cat <<'EOF' > /etc/nftables-host.conf
#!/usr/sbin/nft -f
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Loopback
iif "lo" accept
# Established / related
ct state established,related accept
# SMTP relay (Mailgun)
tcp dport 25 accept
# Proxmox GUI (8006)
tcp dport 8006 ip saddr 192.168.0.0/16 accept
tcp dport 8006 ip6 saddr fe80::/10 accept
tcp dport 8006 ip6 saddr fc00::/7 accept
# Trusted LAN hosts (full access)
ip saddr {
192.168.1.1,
192.168.1.15,
192.168.1.166,
192.168.1.252
} accept
# DNS
udp dport 53 accept
tcp dport 53 accept
# DHCP (IPv4 + IPv6)
udp sport 67 udp dport 68 accept
udp sport 68 udp dport 67 accept
udp dport 546 accept
# mDNS
udp dport 5353 accept
# ICMP (v4 + v6)
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# SSH (management only)
tcp dport 22 ip saddr { 192.168.1.166, 192.168.1.252 } \
ct state new limit rate 3/minute burst 5 packets accept
tcp dport 22 ip saddr { 192.168.1.166, 192.168.1.252 } \
log prefix "SSH-BRUTE: " level info
tcp dport 22 ip saddr { 192.168.1.166, 192.168.1.252 } drop
# Deny SSH from all others
tcp dport 22 log prefix "SSH-DENY: " level info
tcp dport 22 drop
# Generic logging for other drops
limit rate 5/minute burst 20 packets log prefix "DROP: " level info
drop
}
chain forward {
type filter hook forward priority 0; policy accept;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
EOF
# Reload nftables
nft -f /etc/nftables.conf || error " nftables code block invalid - investigate..."
systemctl restart nftables
info "nftables firewall applied."
# Configure nftables logging
touch /var/log/nftables.log
cat <<'EOF' >/etc/rsyslog.d/30-nftables.conf
:msg, contains, "DROP:" -/var/log/nftables.log
& stop
EOF
# Log rotation
cat <<'EOF' >/etc/logrotate.d/nftables
/var/log/nftables.log {
daily
missingok
rotate 7
compress
delaycompress
notifempty
create 0640 root adm
postrotate
systemctl reload rsyslog >/dev/null 2>&1 || true
endscript
}
EOF
systemctl restart rsyslog
info "nftables logging configured."
# ------------------------------------------------------------------------
# 8. Final diagnostics
info "Diagnostics:"
ip addr show "${VM_BRIDGE}" || true
ip route show || true
ip -6 route show || true
ss -lntup | grep -E ':(22)\b' || true
info "✅ Network + SSH setup complete. Log saved to ${LOGFILE}"
I have had no problem with this code and in relation to the names of the ethernet NIC's I adjust them on installation, I believe it's under advanced. Note you will need to adjust sections of this code to suit your hardware and needs.
LXC Configuration
My LXC's are based on Podman (the native repository version). You will definitely need to adjust this code to your needs as most people use docker. I do not for the following reasons
- Podman does not use runc but crun and cgroups version 2
- November crashed my systems, and I am not going down that road again
- Podman does not use a daemon
- It forced me to learn a completely new container system
- Limited hardware - I cannot run up multiple LXCs even with Proxmox's new features
- The base LXC is a Debian 13 container optimized for containers from Community scripts
- You set it up when running the script on your server and can clone it as required
- Additional scripts include - postfix, gpu and fail2ban
#!/bin/bash
# ============================================================
# Proxmox LXC Podman + Conditional Portainer CE Setup Script
# ============================================================
# Purpose:
# - This script uses a Debian 13 LXC container
# - Restructure the entire network
# - Change the proxy, flatten the Podman networks, reduce production servers
# - Proxy on dedicated host - add the tunnel daemon later
# - Completely build the firewall - switch to nftables - breaking change
# - Switch to the trusted host model using nftables
# - Requires the removal and purging of all iptables components and
# carefully resetting nftables without touching podman created filters
# - Remove all the unnecessary code
#
# Version: 9.0.1
#
# Created: 14-11-2025
# Updated: 29-12-2025
# ============================================================
set -euo pipefail
# -----------------------------------------------------------------------
# Utility Functions
# -----------------------------------------------------------------------
info() { echo -e "\033[0;32m[INFO]\033[0m $*"; }
warn() { echo -e "\033[0;33m[WARN]\033[0m $*" >&2; }
error() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; }
# Master host server - hosts Portainer CE - all other host agents
MASTER_HOST=baden
configure_firewall_ports() {
local ports=("$@")
info "Opening firewall ports: ${ports[*]}"
{
echo ""
echo " # Dynamically added ports"
for port in "${ports[@]}"; do
echo " tcp dport $port accept"
done
} >> /etc/nftables-host.conf
# Reload nftables safely (NO flush, NO interference with Podman)
if nft -f /etc/nftables.conf; then
info "Firewall updated successfully for ports: ${ports[*]}"
else
error "Failed to reload nftables after adding ports."
return 1
fi
}
# -----------------------------------------------------------------------
# 1. Base Packages
# -----------------------------------------------------------------------
info "Installing base packages..."
apt-get update -qq || warn "apt-get update failed, check DNS/network"
apt-get install -y -qq \
bind9-dnsutils rsyslog sudo curl gpg net-tools apt-transport-https cron mtr git openssh-server openssh-client \
smartmontools podman podman-docker podman-compose
info "Removing undesired packages..."
for pkg in ufw inetutils-telnet iptables iptables-persistent netfilter-persistent; do
if dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "install ok installed"; then
apt-get purge --auto-remove -y -qq "$pkg"
fi
done
# -----------------------------------------------------------------------
# 2. Miscellaneous Configurations
# -----------------------------------------------------------------------
info "Configuring automatic updates via cron..."
# Ensure the cron job exists
( crontab -l 2>/dev/null || true; \
echo "0 1 * * * apt-get update -qq && apt-get -y -qq upgrade && apt-get -y -qq autoremove && apt-get -y -qq autoclean" \
) | sort -u | crontab -
# Restart cron service depending on distro
if systemctl restart cron.service 2>/dev/null; then
echo "Cron restarted (cron.service)"
elif systemctl restart crond.service 2>/dev/null; then
echo "Cron restarted (crond.service)"
else
echo "Cron restart failed" >&2
exit 1
fi
# Harden the bash history
info "Harden the bash history - ensure nothing is lost..."
export HISTSIZE=50000
export HISTFILESIZE=100000
shopt -s histappend
export PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND"
# Timezone configuration
info "Set the timezone manually to ensure consistency..."
timedatectl set-timezone Australia/Perth
# Ensure timezone is set in /etc/containers/containers.conf
CONF_FILE="/etc/containers/containers.conf"
# Create file if missing
if [ ! -f "$CONF_FILE" ]; then
echo "[engine]" > "$CONF_FILE"
echo 'tz = "Australia/Perth"' >> "$CONF_FILE"
else
# Ensure [engine] section exists
if ! grep -q "^
\[engine\]
" "$CONF_FILE"; then
echo "[engine]" >> "$CONF_FILE"
fi
# Update or append tz line idempotently
if grep -q "^tz" "$CONF_FILE"; then
sed -i 's|^tz.*|tz = "Australia/Perth"|' "$CONF_FILE"
else
sed -i '/^
\[engine\]
/a tz = "Australia/Perth"' "$CONF_FILE"
fi
fi
# Skip downloading extra APT languages
info "Configuring APT to skip downloading extra language files..."
echo 'Acquire::Languages "none";' | tee /etc/apt/apt.conf.d/99-disable-languages
# Force APT to use IPv4 to avoid potential issues
info "Configuring APT to use IPv4..."
echo 'Acquire::ForceIPv4 "true";' | tee /etc/apt/apt.conf.d/99force-ipv4
# Harden bash records - survive across sessions - crashes
info "Reconfiguring bash records..."
export HISTSIZE=10000
export HISTFILESIZE=100000
shopt -s histappend
export PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND"
# -----------------------------------------------------------------------
# 3. Firewall configuration (nftables-native, Podman-safe)
# -----------------------------------------------------------------------
info "Configuring nftables firewall..."
# Host firewall rules (do NOT flush ruleset — Podman owns inet netavark)
cat <<'EOF' > /etc/nftables-host.conf
#!/usr/sbin/nft -f
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Loopback
iif "lo" accept
# Established / related
ct state established,related accept
# Trusted LAN hosts (only these can reach Baden)
ip saddr {
192.168.1.1, # Router
192.168.1.10, # Proxmox
192.168.1.15, # Alex (reverse proxy)
192.168.1.166, # Management workstation 1
192.168.1.252 # Management workstation 2
} accept
# DNS
udp dport 53 accept
tcp dport 53 accept
# DHCP client
udp sport 67 udp dport 68 accept
udp sport 68 udp dport 67 accept
# mDNS
udp dport 5353 accept
# ICMP (v4 + v6)
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# Allow Podman container subnets to reach host
ip saddr 10.88.0.0/16 accept
ip saddr 10.89.1.0/24 accept
ip saddr 10.89.2.0/24 accept
ip saddr 10.89.3.0/24 accept
ip saddr 10.89.4.0/24 accept
ip saddr 10.89.5.0/24 accept
ip saddr 10.89.6.0/24 accept
# SSH from Proxmox only, with brute-force protection
tcp dport 22 ip saddr 192.168.1.10 ct state new limit rate 3/minute burst 5 packets accept
tcp dport 22 ip saddr 192.168.1.10 log prefix "SSH-BRUTE: " level info
tcp dport 22 ip saddr 192.168.1.10 drop
# Deny SSH from all other sources (should be redundant due to trusted hosts rule, but explicit)
tcp dport 22 log prefix "SSH-DENY: " level info
tcp dport 22 drop
# Generic rate-limited logging for other drops
limit rate 5/minute burst 20 packets log prefix "DROP: " level info
drop
}
chain forward {
type filter hook forward priority 0; policy accept;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
EOF
# Loader file — safe for Podman (NO flush ruleset)
cat <<'EOF' > /etc/nftables.conf
#!/usr/sbin/nft -f
include "/etc/nftables-host.conf"
EOF
info "Loading nftables ruleset..."
if ! nft -f /etc/nftables.conf; then
error "Failed to load nftables ruleset; firewall not applied."
exit 1
fi
# -----------------------------------------------------------------------
# 4. Firewall logging (rsyslog + logrotate)
# -----------------------------------------------------------------------
info "Configuring nftables logging..."
cat <<'EOF' > /etc/rsyslog.d/nftables.conf
:msg, contains, "SSH-BRUTE" -/var/log/iptables-dropped.log
:msg, contains, "SSH-DENY" -/var/log/iptables-dropped.log
:msg, contains, "DROP:" -/var/log/iptables-dropped.log
& stop
EOF
touch /var/log/iptables-dropped.log
cat <<'EOF' > /etc/logrotate.d/nftables
/var/log/iptables-dropped.log {
daily
missingok
rotate 7
compress
delaycompress
notifempty
create 0640 root adm
postrotate
systemctl reload rsyslog >/dev/null 2>&1 || true
endscript
}
EOF
systemctl restart rsyslog || warn "Failed to restart rsyslog"
# -----------------------------------------------------------------------
# 5. Sysctl configuration
# -----------------------------------------------------------------------
info "Configuring sysctl settings..."
# Enable IPv4 forwarding and RA acceptance on bridge
info "Applying IPv4 sysctl tuning..."
tee /etc/sysctl.d/99-ipv4-lxc.conf >/dev/null <<EOF
net.ipv4.ip_forward=0
EOF
# Enable IPv6 forwarding and RA acceptance on bridge
info "Applying IPv6 sysctl tuning..."
tee /etc/sysctl.d/99-ipv6-lxc.conf >/dev/null <<EOF
net.ipv6.conf.all.forwarding=0
net.ipv6.conf.eth0.accept_ra=2
EOF
sysctl --system || warn "Failed to apply IPv4/IPv6 sysctl settings"
warn "Some warning are normal due to the fact its a LXC...."
# -----------------------------------------------------------------------
# 6. Podman Registry Config & Network Setup
# -----------------------------------------------------------------------
info "Configuring Podman registries..."
mkdir -p /etc/containers/registries.conf.d
cat <<'EOF' > /etc/containers/registries.conf.d/99-unqualified.conf
# Managed by setup-lxc-script.sh (${BACKUP_TS})
unqualified-search-registries = ["docker.io", "quay.io", "ghcr.io", "registry.fedoraproject.org"]
[aliases]
# Map unqualified image names to fully qualified ones
"portainer/agent" = "docker.io/portainer/agent"
"portainer/portainer-ce" = "docker.io/portainer/portainer-ce"
EOF
# Create Podman networks - modified.
info "Creating Podman network -"
podman network create app-net
# Restart the following services
systemctl start podman && systemctl enable podman
systemctl restart podman.socket && systemctl enable podman.socket
# Enable Podman to restart all containers With Restart Policy Set To Always.
systemctl start podman-restart.service && systemctl enable podman-restart.service
systemctl restart podman.socket && systemctl enable podman.socket
# Enable the auto update function that requires an additional label in the service stacks
systemctl enable podman-auto-update.service
systemctl enable podman-auto-update.timer
systemctl start podman-auto-update.timer
# Enable the clean function so that it runs on boot - there is no timer service
systemctl enable --now podman-clean-transient.service
# Ensure the cron job exists (deduplicated)
( crontab -l 2>/dev/null || true; \
echo "0 21 * * * /usr/bin/systemctl start podman-clean-transient.service" \
) | sort -u | crontab -
# Restart cron service depending on distro
if systemctl restart cron.service 2>/dev/null; then
echo "Cron restarted (cron.service)"
elif systemctl restart crond.service 2>/dev/null; then
echo "Cron restarted (crond.service)"
else
echo "Cron restart failed" >&2
exit 1
fi
# -----------------------------------------------------------------------
# 7. Conditional Portainer Deployment
# -----------------------------------------------------------------------
HOST_SHORT=$(hostname -s || echo "unknown")
info "Detected short hostname: $HOST_SHORT"
if [[ "$HOST_SHORT" == "$MASTER_HOST" ]]; then
info "Primary host — deploying Portainer CE..."
# Ensure Podman does not reuse CE name
podman rm -f portainer-ce >/dev/null 2>&1 || true
# Build base create command as an array (safer than eval)
podman create --name portainer-ce \
--restart=always \
--privileged \
--no-healthcheck \
-p 9000:9000 -p 9443:9443 \
-v /run/podman/podman.sock:/var/run/docker.sock \
-v portainer_data:/data \
docker.io/portainer/portainer-ce:latest
# Generate systemd unit
podman generate systemd --name portainer-ce --files --new
mv container-portainer-ce.service /etc/systemd/system/portainer-ce.service
# Enable and start
systemctl daemon-reexec && systemctl daemon-reload
systemctl start portainer-ce.service && systemctl enable --now portainer-ce.service
configure_firewall_ports 9000 9443
info "Portainer CE deployed."
else
info "Secondary host — deploying Portainer Agent (requires privileged)..."
# Ensure Podman does not reuse CE name
podman rm -f portainer-agent >/dev/null 2>&1 || true
# Agent requires access to the container runtime socket and listens on 9001 - requires privileged
podman create --name portainer-agent \
--restart=always \
--privileged \
--no-healthcheck \
-p 9001:9001 \
-v /run/podman/podman.sock:/var/run/docker.sock \
-v portainer_agent_data:/var/lib/docker/volumes \
-v /:/host \
docker.io/portainer/agent:latest
# Generate systemd unit
podman generate systemd --name portainer-agent --files --new
mv container-portainer-agent.service /etc/systemd/system/portainer-agent.service
# Enable and start
systemctl daemon-reexec && systemctl daemon-reload
systemctl start portainer-agent.service && systemctl enable --now portainer-agent.service
configure_firewall_ports 9001
info "Portainer Agent deployed."
fi
# -----------------------------------------------------------------------
# 8. SSH Configuration Hardening
# -----------------------------------------------------------------------
info "Hardening SSH configuration..."
SSHD_DIR="/etc/ssh"
SSHD_CFG="${SSHD_DIR}/sshd_config"
BACKUP_TS="$(date +%Y%m%d-%H%M%S)"
BACKUP_DIR="${SSHD_DIR}/backup-${BACKUP_TS}"
mkdir -p "${BACKUP_DIR}"
info "Backing up sshd_config and host keys..."
cp -a "${SSHD_CFG}" "${BACKUP_DIR}/sshd_config.bak" || true
info "Writing hardened sshd_config..."
cat <<'EOF' > "${SSHD_CFG}"
# Managed by setup-lxc-v6.sh (${BACKUP_TS})
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
HostbasedAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
LoginGraceTime 30
MaxAuthTries 4
MaxSessions 10
HostKey ${SSHD_DIR}/ssh_host_ed25519_key
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
UseDNS no
PrintMotd no
PrintLastLog yes
AcceptEnv LANG LC_*
# Permit root password auth only from RFC1918 and link-local IPv6
Match Address 10.0.0.0/8,192.168.0.0/16,fe80::/10
PermitRootLogin yes
PasswordAuthentication yes
AuthenticationMethods any
EOF
info "Validating sshd_config..."
if sshd -t; then
info "sshd_config syntax OK, restarting ssh..."
if systemctl restart ssh; then
info "sshd restarted successfully."
else
error "systemctl restart ssh failed — check service status."
fi
else
error "sshd_config test failed — not restarting ssh."
fi
# -----------------------------------------------------------------------
# 9. Final Notes
# -----------------------------------------------------------------------
info " Deployment complete."
info " Portainer stacks still need watchtower
info " Portainer and agents will auto update"
warn " Please run the LXC-GPU code post reboot after this script has completed."
warn " Please ensure that the LXC container configuration file meets prototype kept on file"
warn " Please reboot the system to ensure all settings take full effect."
This should get you up and running but as above you will definitely need to edit this script depending on your container system, the SSH rules, the trusted hosts and so forth.
I have proof read this multiple times, but omissions may still occur
I have rebooted multiple times and found no major issues
I have port scanned multiple times.
The proxy configuration has been shared under another post located here

Any how sharing is caring and this might help someone
#enoughsaid



