Container: Debian bookworm · Host bridge:
lxcbr0 · IPv4: 10.0.99.10 · IPv6: SLAAC via radvd · Web: Caddy reverse proxy · Container SSH on port 22⚠️ IPv6 TCP note: ICMP and HTTP via Caddy (IPv4) work perfectly. Native IPv6 TCP (SSH, curl) requires the VPS provider's upstream IPv6 gateway to forward return traffic — some budget VPS providers have broken IPv6 TCP. Test with
ping -6 and ssh -6 from your local machine.VPS LXC Container — Dual-Stack Networking (IPv4 + IPv6 SLAAC)
Tested on: VPS Debian 12 (Bookworm) · LXC 5.0.2 · Caddy 2 · radvd 2.19 · VPS: KVM AMD EPYC
1. Host Setup
Install packages
apt update && apt install -y lxc debootstrap bridge-utils iptables-persistent socat radvd
socat for TCP relay debugging. radvd for IPv6 SLAAC advertisement.
Enable IP forwarding (IPv4 + IPv6)
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.d/99-lxc.conf
echo 'net.ipv6.conf.all.forwarding = 1' >> /etc/sysctl.d/99-lxc.conf
echo 'net.ipv6.conf.all.accept_ra = 0' >> /etc/sysctl.d/99-lxc.conf
sysctl -p /etc/sysctl.d/99-lxc.conf
accept_ra = 0 prevents the container from accepting router advertisements that could override static IPv6 config.
Check cgroup version
cat /sys/fs/cgroup/cpu.max
# "100000 100000" = cgroup2 cpu controller active (Debian 12 default)
Create bridge (lxcbr0)
The default LXC bridge uses 10.0.99.0/24 — use it instead of creating your own:
# /etc/default/lxc-net — enable the default bridge
USE_LXC_BRIDGE="true"
LXC_BRIDGE="lxcbr0"
LXC_ADDR="10.0.99.1"
LXC_NETWORK="10.0.99.0/24"
LXC_DHCP_CONFILE=""
systemctl enable --now lxc-net
Verify:
ip addr show lxcbr0
# Expected: inet 10.0.99.1/24
Set FORWARD policy to ACCEPT
iptables -P FORWARD ACCEPT
# Survives reboots via iptables-persistent
2. Create Container (debootstrap)
mkdir -p /var/lib/lxc/debian-ct/rootfs
debootstrap bookworm /var/lib/lxc/debian-ct/rootfs http://deb.debian.org/debian
3. Configure Container Network
Edit /var/lib/lxc/debian-ct/rootfs/etc/network/interfaces:
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address 10.0.99.10/24
gateway 10.0.99.1
3b. Disable systemd-networkd (recommended)
Debian 12 runs systemd-networkd which auto-manages interfaces and conflicts with static /etc/network/interfaces config. Mask it inside the container:
lxc-attach -n debian-ct -- systemctl mask systemd-networkd systemd-resolved
lxc-attach -n debian-ct -- rm -f /etc/resolv.conf
lxc-attach -n debian-ct -- sh -c 'echo "nameserver 8.8.8.8" > /etc/resolv.conf'
Then configure static IP as shown above.
Set hostname and hosts:
echo 'debian-ct' > /var/lib/lxc/debian-ct/rootfs/etc/hostname
cat > /var/lib/lxc/debian-ct/rootfs/etc/hosts << 'EOF'
127.0.0.1 localhost
127.0.1.1 debian-ct
::1 localhost ip6-localhost ip6-loopback
EOF
4. LXC Config
# ── UPDATE THESE 3 LINES before starting ──
lxc.uts.name = debian-ct
lxc.rootfs.path = dir:/var/lib/lxc/debian-ct/rootfs
lxc.net.0.hwaddr = 00:11:22:33:44:55
lxc.include = /usr/share/lxc/config/debian.conf
lxc.arch = linux64
# Memory cap
lxc.cgroup2.memory.max = 512M
lxc.cgroup2.memory.high = 384M
# CPU & process limits
lxc.cgroup2.cpu.weight = 102
# lxc.cgroup2.cpu.max = "1 4" # comment out if cpu controller unavailable
lxc.cgroup2.memory.swap.max = 256M
lxc.cgroup2.pids.max = 1024
# Autostart on boot
lxc.start.auto = 1
lxc.start.delay = 0
# Network: veth → lxcbr0
lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
# Mount auto — cgroup2 access
lxc.mount.auto = proc:mixed sys:mixed cgroup:rw:force
Generate a unique MAC:
openssl rand -hex 6 | sed 's/\(..\)/\1:/g'
5. IPv6 — SLAAC via radvd
5a. Assign IPv6 to host bridge
ip -6 addr add 2607:f130:0:100::11/64 dev lxcbr0
Make persistent via systemd-networkd:
cat > /etc/systemd/network/lxcbr0.network << 'EOF'
[Match]
Name=lxcbr0
[Network]
Address=10.0.99.1/24
Address=2607:f130:0:100::11/64
EOF
systemctl daemon-reload && systemctl restart systemd-networkd
5b. Configure radvd
radvd v2.19 compat:
AdvOtherFlagwas removed in v2.19. Omit it — the flag is obsolete.
cat > /etc/radvd.conf << 'EOF'
interface lxcbr0 {
AdvSendAdvert on;
prefix 2607:f130:0:100::/64 {
AdvOnLink on;
AdvAutonomous on;
};
};
EOF
systemctl enable --now radvd
5c. IPv6 MASQUERADE (for outbound IPv6 from container)
ip6tables -t nat -A POSTROUTING -s 2607:f130:0:100::/64 ! -d 2607:f130:0:100::/64 -j MASQUERADE
Make persistent:
# Add to /etc/iptables/rules.v6 or via iptables-persistent
5d. Host routing — direct IPv6 route to container
The host needs a specific /128 route to reach the container's IPv6, since both ens3 and lxcbr0 share the same /64:
ip -6 route add YOUR_PREFIX:SUFFIX:4ff:fe0e:376c/128 dev lxcbr0
Make persistent:
cat >> /etc/network/interfaces << 'EOF'
# IPv6 route for LXC container
up ip -6 route add YOUR_PREFIX:SUFFIX:4ff:fe0e:376c/128 dev lxcbr0
EOF
Also enable proxy-NDP on the physical interface so the host responds to ND requests for the container's IPv6:
sysctl -w net.ipv6.conf.ens3.proxy_ndp=1
ip -6 neigh add proxy YOUR_PREFIX:SUFFIX:4ff:fe0e:376c dev ens3
Make persistent:
echo 'net.ipv6.conf.ens3.proxy_ndp = 1' >> /etc/sysctl.d/99-lxc-ipv6.conf
5e. Container IPv6 default route
After radvd starts, the container auto-learns its IPv6 prefix via SLAAC and sets a default route. Verify inside the container:
lxc-attach -n debian-ct -- ip -6 addr show eth0
# Expected: inet6 2607:f130:0:100:.../64 scope global dynamic mngtmpaddr
lxc-attach -n debian-ct -- ip -6 route | grep default
# Expected: default via fe80::... dev eth0 proto ra
6. Different Credentials — Container vs Host
Container SSH uses its own password, separate from the host root password:
# Set container root password
lxc-attach -n debian-ct -- chpasswd << 'CREDS'
root:LxcDebian2026
CREDS
Enable SSH inside the container:
lxc-attach -n debian-ct -- apt-get install -y openssh-server
lxc-attach -n debian-ct -- systemctl enable --now ssh
Disable DNS lookups in container SSH (prevents banner timeout on broken networks):
lxc-attach -n debian-ct -- sh -c 'echo "UseDNS no" >> /etc/ssh/sshd_config'
lxc-attach -n debian-ct -- systemctl restart ssh
Container SSH now listens on:
- IPv4:
10.0.99.10:22 - IPv6:
[YOUR_PREFIX:SUFFIX:4ff:fe0e:376c]:22
Login: root / LxcDebian2026
7. Caddy Reverse Proxy (Host → Container)
Install a web server inside the container:
lxc-attach -n debian-ct -- apt-get install -y python3
lxc-attach -n debian-ct -- mkdir -p /var/www/html
cat > /var/lib/lxc/debian-ct/rootfs/var/www/html/index.html << 'EOF'
<html><body><h1>LXC Debian Test</h1><p>Caddy proxy works!</p></body></html>
EOF
Start the web server (daemonize with nohup + setsid so it outlives lxc-attach):
lxc-attach -n debian-ct -- sh -c 'nohup python3 -m http.server 9090 > /var/log/http.log 2>&1 &'
Configure Caddy on the host (/etc/caddy/Caddyfile):
https://898998.xyz/lxc_test_page/ {
reverse_proxy / http://10.0.99.10:9090
}
Note: If Docker is on the host, it may intercept port 8080 via a NAT rule (
DNAT 6 -- 0.0.0.0/0 !127.0.0.0/8 tcp dpt:8080 to:172.18.0.2:8080). Use a different port like9090.
systemctl reload caddy
Test: https://898998.xyz/lxc_test_page/ → 200 OK
8. Quick Reference
Container access
# Attach (run commands inside)
lxc-attach -n debian-ct
# SSH from host
ssh [email protected]
# Password: LxcDebian2026
# SSH via IPv6 (from local machine, if provider IPv6 TCP works)
ssh -6 root@YOUR_PREFIX:SUFFIX:4ff:fe0e:376c
# Password: LxcDebian2026
Container management
lxc-start -n debian-ct -d # start
lxc-stop -n debian-ct -t 10 # stop
lxc-info -n debian-ct # status
lxc-monitor -n debian-ct # live events
IPv6 verification commands
# Host bridge IPv6
ip -6 addr show lxcbr0 | grep 'scope global'
# radvd running
systemctl status radvd
# Container IPv6 (SLAAC assigned)
lxc-attach -n debian-ct -- ip -6 addr show eth0 | grep 'scope global'
# Ping container from host
ping -6 YOUR_PREFIX:SUFFIX:4ff:fe0e:376c
IPv6 TCP troubleshooting
# Test ICMP (works even if TCP broken)
ping -6 YOUR_PREFIX:SUFFIX:4ff:fe0e:376c
# Test SSH banner (reaches server, checks sshd responds)
timeout 5 bash -c 'cat < /dev/tcp/YOUR_PREFIX:SUFFIX:4ff:fe0e:376c/22'
# Expected: SSH-2.0-OpenSSH_...
# If ICMP works but TCP fails — provider IPv6 upstream issue
# Symptom: SYN reaches, SYN-ACK never returns
# Fix: None from VPS side — use IPv4 or wait for provider fix
9. System Resource Usage
| Resource | Host (idle) | Container (running) |
|---|---|---|
| CPU | ~0% | negligible |
| RAM | ~377MB | ~50MB (Debian minimal) |
| Disk | 66GB total | rootfs ~500MB |
| IPv4 bridge | 10.0.99.1/24 | 10.0.99.10/24 |
| IPv6 bridge | 2607:f130:0:100::11/64 | SLAAC (2607:f130:0:100:...) |
LXC adds almost no CPU overhead. Memory footprint is minimal — the container only uses what runs inside it.