Skip to content

Instantly share code, notes, and snippets.

@sjelfull
Last active January 29, 2026 09:58
Show Gist options
  • Select an option

  • Save sjelfull/cb327ad5e5506cad7f540c16116a61e3 to your computer and use it in GitHub Desktop.

Select an option

Save sjelfull/cb327ad5e5506cad7f540c16116a61e3 to your computer and use it in GitHub Desktop.
Proxmox LXC setup script for Dante SOCKS5 proxy - route traffic through residential IP via Tailscale exit node

Proxmox Dante SOCKS5 Proxy LXC

Automated setup script for creating a minimal Dante SOCKS5 proxy in a Proxmox LXC container. The proxy sits on your home LAN and is accessed through your existing Tailscale exit node.

Architecture

┌─────────────┐     Tailscale      ┌─────────────┐       LAN        ┌─────────────┐
│   Fly.io    │ ────────────────── │  Exit Node  │ ──────────────── │  Dante LXC  │
│  (yt-dlp)   │       mesh         │    LXC      │   192.168.x.x    │   (proxy)   │
└─────────────┘                    └─────────────┘                  └──────┬──────┘
                                                                           │
                                                                           ▼
                                                                    ┌─────────────┐
                                                                    │ Home Router │
                                                                    │(residential)│
                                                                    └──────┬──────┘
                                                                           │
                                                                           ▼
                                                                      ┌─────────┐
                                                                      │ YouTube │
                                                                      └─────────┘

Traffic flow:

  1. Fly.io connects through Tailscale mesh to your exit node
  2. Exit node forwards request to Dante LXC on your LAN
  3. Dante accepts the connection and makes outbound request
  4. Traffic exits via your home router's residential IP
  5. YouTube sees residential IP, not Fly.io datacenter IP

Prerequisites

  • Proxmox VE 7.x or 8.x
  • Existing Tailscale exit node on your LAN with LAN access enabled:
    tailscale up --advertise-exit-node --exit-node-allow-lan-access

Quick Start

SSH into your Proxmox host as root and run:

bash -c "$(curl -fsSL <GIST_RAW_URL>/proxmox-dante-setup.sh)"

Interactive Prompts

Setting Default Description
Container ID Next available Proxmox VMID for the new LXC
Hostname dante-proxy Container hostname
Memory 512 MB RAM allocation
Disk 4 GB Root disk size
Storage local-lvm Proxmox storage for the container
Bridge vmbr0 Network bridge
SOCKS5 Port 1080 Port Dante listens on

What Gets Installed

  • Debian 12 minimal LXC container (~200MB)
  • Dante - SOCKS5 proxy listening on LAN interface

No Tailscale needed in this container — it's accessed via your existing exit node.

Post-Installation

Update your Fly.io yt-dlp config:

--proxy socks5://192.168.x.x:1080  # Use the LAN IP shown by the script

Testing

# From your exit node LXC - should return your home IP
curl -x socks5://192.168.x.x:1080 https://ifconfig.me

Troubleshooting

Dante not starting

danted -V -f /etc/danted.conf  # Check config syntax
journalctl -u danted -f        # Check logs

Connection refused from Fly.io

  1. Ensure exit node has --exit-node-allow-lan-access enabled
  2. Verify Fly.io can reach the exit node: tailscale ping <exit-node-hostname>

Security Notes

  • Dante accepts connections from RFC1918 private ranges and Tailscale CGNAT (100.64.x)
  • No authentication required (access controlled by network topology)
  • Only accessible from your LAN and through the exit node

Uninstalling

pct stop <VMID>
pct destroy <VMID>

Related Resources

#!/usr/bin/env bash
# Proxmox Dante SOCKS5 Proxy LXC Setup Script
# Creates a minimal LXC container with Dante SOCKS5 proxy for routing traffic
# through a residential IP (useful for yt-dlp, avoiding datacenter IP blocks)
#
# Architecture:
# Fly.io → Tailscale mesh → Exit Node LXC → Dante LXC (LAN) → Internet
#
# The Dante LXC sits on your home LAN (no Tailscale needed). Fly.io connects
# through your existing Tailscale exit node, which can reach the Dante LXC
# on the local network. Traffic exits via your home router's residential IP.
#
# Usage: Run on Proxmox host as root
# bash -c "$(curl -fsSL https://gist.githubusercontent.com/.../proxmox-dante-setup.sh)"
#
# Inspired by tteck's Proxmox helper scripts
set -euo pipefail
# =============================================================================
# CONFIGURATION DEFAULTS
# =============================================================================
DEFAULT_CTID="$(pvesh get /cluster/nextid 2>/dev/null || echo 100)"
DEFAULT_HOSTNAME="dante-proxy"
DEFAULT_MEMORY="512"
DEFAULT_DISK="4"
DEFAULT_CORES="1"
DEFAULT_STORAGE="local-lvm"
DEFAULT_TEMPLATE="debian-12-standard_12.2-1_amd64.tar.zst"
DEFAULT_BRIDGE="vmbr0"
DEFAULT_DANTE_PORT="1080"
# =============================================================================
# COLORS AND OUTPUT HELPERS
# =============================================================================
RD=$(echo "\033[01;31m")
GN=$(echo "\033[1;92m")
YW=$(echo "\033[33m")
BL=$(echo "\033[36m")
CL=$(echo "\033[m")
CM="${GN}✓${CL}"
CROSS="${RD}✗${CL}"
SPINNER="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
header_info() {
clear
cat <<"EOF"
____ __ ____
/ __ \____ _____ / /____ / __ \_________ _ ____ __
/ / / / __ `/ __ \/ __/ _ \ / /_/ / ___/ __ \| |/_/ / / /
/ /_/ / /_/ / / / / /_/ __/ / ____/ / / /_/ /> </ /_/ /
/_____/\__,_/_/ /_/\__/\___/ /_/ /_/ \____/_/|_|\__, /
/____/
SOCKS5 Proxy for Proxmox LXC
EOF
echo
}
msg_info() {
local msg="$1"
echo -ne " ${BL}${SPINNER:0:1}${CL} ${msg}..."
}
msg_ok() {
local msg="$1"
echo -e "\r ${CM} ${msg} "
}
msg_error() {
local msg="$1"
echo -e "\r ${CROSS} ${msg} "
}
msg_warn() {
local msg="$1"
echo -e " ${YW}⚠${CL} ${msg}"
}
# =============================================================================
# VALIDATION FUNCTIONS
# =============================================================================
check_root() {
if [[ $EUID -ne 0 ]]; then
msg_error "This script must be run as root on the Proxmox host"
exit 1
fi
}
check_proxmox() {
if ! command -v pct &>/dev/null; then
msg_error "This script must be run on a Proxmox VE host"
exit 1
fi
}
check_template() {
local storage="$1"
local template="$2"
if ! pveam list "$storage" 2>/dev/null | grep -q "$template"; then
msg_info "Downloading Debian 12 template"
pveam download "$storage" "$template" &>/dev/null
msg_ok "Downloaded Debian 12 template"
fi
}
validate_ctid() {
local ctid="$1"
if [[ ! "$ctid" =~ ^[0-9]+$ ]] || [[ "$ctid" -lt 100 ]]; then
msg_error "Invalid container ID: $ctid"
return 1
fi
if pct status "$ctid" &>/dev/null; then
msg_error "Container ID $ctid already exists"
return 1
fi
return 0
}
# =============================================================================
# INTERACTIVE PROMPTS
# =============================================================================
prompt_settings() {
echo -e "${BL}Container Settings${CL}"
echo -e "${YW}─────────────────────────────────────────${CL}"
# Container ID
while true; do
read -rp " Container ID [$DEFAULT_CTID]: " CTID
CTID="${CTID:-$DEFAULT_CTID}"
validate_ctid "$CTID" && break
done
# Hostname
read -rp " Hostname [$DEFAULT_HOSTNAME]: " HOSTNAME
HOSTNAME="${HOSTNAME:-$DEFAULT_HOSTNAME}"
# Memory
read -rp " Memory in MB [$DEFAULT_MEMORY]: " MEMORY
MEMORY="${MEMORY:-$DEFAULT_MEMORY}"
# Disk
read -rp " Disk size in GB [$DEFAULT_DISK]: " DISK
DISK="${DISK:-$DEFAULT_DISK}"
# Storage
read -rp " Storage [$DEFAULT_STORAGE]: " STORAGE
STORAGE="${STORAGE:-$DEFAULT_STORAGE}"
# Bridge
read -rp " Network bridge [$DEFAULT_BRIDGE]: " BRIDGE
BRIDGE="${BRIDGE:-$DEFAULT_BRIDGE}"
echo
echo
echo -e "${BL}Dante Proxy Settings${CL}"
echo -e "${YW}─────────────────────────────────────────${CL}"
# Dante port
read -rp " SOCKS5 port [$DEFAULT_DANTE_PORT]: " DANTE_PORT
DANTE_PORT="${DANTE_PORT:-$DEFAULT_DANTE_PORT}"
echo
}
confirm_settings() {
echo -e "${BL}Configuration Summary${CL}"
echo -e "${YW}─────────────────────────────────────────${CL}"
echo -e " Container ID: ${GN}$CTID${CL}"
echo -e " Hostname: ${GN}$HOSTNAME${CL}"
echo -e " Memory: ${GN}${MEMORY}MB${CL}"
echo -e " Disk: ${GN}${DISK}GB${CL}"
echo -e " Storage: ${GN}$STORAGE${CL}"
echo -e " Bridge: ${GN}$BRIDGE${CL}"
echo -e " SOCKS5 Port: ${GN}$DANTE_PORT${CL}"
echo
read -rp "Proceed with installation? [y/N]: " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "Installation cancelled."
exit 0
fi
}
# =============================================================================
# LXC CREATION
# =============================================================================
create_lxc() {
msg_info "Creating LXC container $CTID"
# Check/download template
local template_storage="${STORAGE%%:*}"
[[ "$template_storage" == "$STORAGE" ]] && template_storage="local"
check_template "$template_storage" "$DEFAULT_TEMPLATE"
# Create container
pct create "$CTID" "${template_storage}:vztmpl/${DEFAULT_TEMPLATE}" \
--hostname "$HOSTNAME" \
--memory "$MEMORY" \
--cores "$DEFAULT_CORES" \
--rootfs "${STORAGE}:${DISK}" \
--net0 "name=eth0,bridge=${BRIDGE},ip=dhcp" \
--unprivileged 1 \
--features "nesting=1" \
--onboot 1 \
--start 0 \
&>/dev/null
msg_ok "Created LXC container $CTID"
}
start_lxc() {
msg_info "Starting LXC container"
pct start "$CTID" &>/dev/null
sleep 3
# Wait for network
local retries=0
while ! pct exec "$CTID" -- ping -c 1 8.8.8.8 &>/dev/null; do
sleep 1
((retries++))
if [[ $retries -gt 30 ]]; then
msg_error "Container failed to get network connectivity"
exit 1
fi
done
msg_ok "Started LXC container"
}
# =============================================================================
# SOFTWARE INSTALLATION
# =============================================================================
install_packages() {
msg_info "Updating system and installing packages"
pct exec "$CTID" -- bash -c "
apt-get update &>/dev/null
apt-get install -y dante-server &>/dev/null
"
msg_ok "Installed Dante"
}
configure_dante() {
msg_info "Configuring Dante SOCKS5 proxy"
# Get the LAN interface and IP
local lan_iface
local lan_ip
lan_iface=$(pct exec "$CTID" -- ip route get 8.8.8.8 | grep -oP 'dev \K\S+' | head -1)
lan_ip=$(pct exec "$CTID" -- ip -4 addr show "$lan_iface" | grep -oP 'inet \K[\d.]+' | head -1)
if [[ -z "$lan_ip" ]]; then
msg_error "Could not determine LAN IP"
exit 1
fi
# Create config file on Proxmox host, then push to container
# This avoids all the heredoc/quoting issues with pct exec
local tmp_config
tmp_config=$(mktemp)
cat > "$tmp_config" << EOF
# Dante SOCKS5 Proxy Configuration
# Generated by proxmox-dante-setup.sh
logoutput: syslog
internal: ${lan_iface} port = ${DANTE_PORT}
external: ${lan_iface}
socksmethod: none
# Client rules - allow private networks and Tailscale
client pass { from: 10.0.0.0/8 to: 0.0.0.0/0 }
client pass { from: 172.16.0.0/12 to: 0.0.0.0/0 }
client pass { from: 192.168.0.0/16 to: 0.0.0.0/0 }
client pass { from: 100.64.0.0/10 to: 0.0.0.0/0 }
client block { from: 0.0.0.0/0 to: 0.0.0.0/0 }
# SOCKS rules - allow all destinations
socks pass { from: 0.0.0.0/0 to: 0.0.0.0/0 }
EOF
# Push config file into container
pct push "$CTID" "$tmp_config" /etc/danted.conf
rm -f "$tmp_config"
# Enable and start Dante
pct exec "$CTID" -- systemctl enable danted &>/dev/null
pct exec "$CTID" -- systemctl restart danted
# Store LAN IP for later
LAN_IP="$lan_ip"
msg_ok "Configured Dante (${lan_iface}: ${lan_ip}:${DANTE_PORT})"
}
# =============================================================================
# VERIFICATION
# =============================================================================
verify_setup() {
msg_info "Verifying setup"
# Check Dante is listening
if ! pct exec "$CTID" -- ss -tlnp | grep -q ":${DANTE_PORT}"; then
msg_error "Dante is not listening on port $DANTE_PORT"
echo
echo "Debug info:"
pct exec "$CTID" -- systemctl status danted
exit 1
fi
# Test proxy with external request
local public_ip
public_ip=$(pct exec "$CTID" -- curl -s -x "socks5://127.0.0.1:${DANTE_PORT}" https://ifconfig.me 2>/dev/null || echo "failed")
if [[ "$public_ip" == "failed" ]]; then
msg_warn "Could not verify proxy (may still work)"
else
msg_ok "Verified setup (Public IP: $public_ip)"
fi
}
print_summary() {
echo
echo -e "${GN}═══════════════════════════════════════════════════════════════${CL}"
echo -e "${GN} SETUP COMPLETE! ${CL}"
echo -e "${GN}═══════════════════════════════════════════════════════════════${CL}"
echo
echo -e " ${BL}Container:${CL} $CTID ($HOSTNAME)"
echo -e " ${BL}LAN IP:${CL} $LAN_IP"
echo -e " ${BL}SOCKS5 Proxy:${CL} socks5://${LAN_IP}:${DANTE_PORT}"
echo
echo -e " ${YW}To use with yt-dlp on Fly.io:${CL}"
echo -e " --proxy socks5://${LAN_IP}:${DANTE_PORT}"
echo
echo -e " ${YW}Or set environment variable:${CL}"
echo -e " YTDLP_PROXY=socks5://${LAN_IP}:${DANTE_PORT}"
echo
echo -e " ${YW}Test from your Tailscale exit node:${CL}"
echo -e " curl -x socks5://${LAN_IP}:${DANTE_PORT} https://ifconfig.me"
echo
echo -e " ${YW}Note:${CL} Fly.io connects through your Tailscale exit node,"
echo -e " which can reach this proxy on your LAN."
echo
echo -e "${GN}═══════════════════════════════════════════════════════════════${CL}"
}
# =============================================================================
# MAIN
# =============================================================================
main() {
header_info
check_root
check_proxmox
prompt_settings
confirm_settings
echo
create_lxc
start_lxc
install_packages
configure_dante
verify_setup
print_summary
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment