Skip to content

Instantly share code, notes, and snippets.

@liwsakilive
Created April 26, 2026 18:36
Show Gist options
  • Select an option

  • Save liwsakilive/16d13a04699dedf521a830fe1d00456c to your computer and use it in GitHub Desktop.

Select an option

Save liwsakilive/16d13a04699dedf521a830fe1d00456c to your computer and use it in GitHub Desktop.
#!/bin/bash
# =============================================================================
# setup-cloudflared.sh
# Cloudflare Tunnel setup for Debian LXC (Proxmox)
# Zotect Digital Capital Co., Ltd.
# =============================================================================
# Usage:
# chmod +x setup-cloudflared.sh
# ./setup-cloudflared.sh <TUNNEL_TOKEN>
# =============================================================================
set -euo pipefail
# --------------------------------------------------------------------------- #
# Config
# --------------------------------------------------------------------------- #
CF_USER="cloudflared"
CF_GROUP="cloudflared"
PING_GROUP_RANGE="0 65534"
LOG_TAG="[cloudflared-setup]"
# --------------------------------------------------------------------------- #
# Helpers
# --------------------------------------------------------------------------- #
info() { echo -e "\e[32m${LOG_TAG} INFO $*\e[0m"; }
warn() { echo -e "\e[33m${LOG_TAG} WARN $*\e[0m"; }
error() { echo -e "\e[31m${LOG_TAG} ERROR $*\e[0m"; exit 1; }
section() { echo -e "\n\e[36m══════════════════════════════════════\e[0m"; \
echo -e "\e[36m $*\e[0m"; \
echo -e "\e[36m══════════════════════════════════════\e[0m"; }
# --------------------------------------------------------------------------- #
# Pre-flight checks
# --------------------------------------------------------------------------- #
section "Pre-flight Checks"
[[ $EUID -ne 0 ]] && error "กรุณารันด้วย root: sudo $0 <TOKEN>"
[[ $# -lt 1 ]] && error "Usage: $0 <TUNNEL_TOKEN>"
TUNNEL_TOKEN="$1"
info "Tunnel token received ✓"
info "OS: $(grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '\"')"
info "Arch: $(uname -m)"
# --------------------------------------------------------------------------- #
# Install cloudflared
# --------------------------------------------------------------------------- #
section "Installing cloudflared"
info "Adding Cloudflare GPG key..."
apt-get update -qq
apt-get install -y -qq curl gnupg lsb-release
mkdir -p /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
| gpg --dearmor -o /usr/share/keyrings/cloudflare-main.gpg
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] \
https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" \
> /etc/apt/sources.list.d/cloudflared.list
info "Installing cloudflared package..."
apt-get update -qq
apt-get install -y -qq cloudflared
INSTALLED_VER=$(cloudflared --version 2>&1 | head -1)
info "Installed: ${INSTALLED_VER}"
# --------------------------------------------------------------------------- #
# Create dedicated user/group
# --------------------------------------------------------------------------- #
section "Creating cloudflared User"
if ! getent group "${CF_GROUP}" > /dev/null 2>&1; then
groupadd --system "${CF_GROUP}"
info "Created group: ${CF_GROUP}"
else
warn "Group ${CF_GROUP} already exists — skipping"
fi
if ! id "${CF_USER}" > /dev/null 2>&1; then
useradd --system \
--gid "${CF_GROUP}" \
--no-create-home \
--shell /bin/false \
"${CF_USER}"
info "Created user: ${CF_USER} (GID: $(id -g ${CF_USER}))"
else
warn "User ${CF_USER} already exists — skipping"
fi
# --------------------------------------------------------------------------- #
# Fix ping_group_range (resolve ICMP/GID warning)
# --------------------------------------------------------------------------- #
section "Fixing ping_group_range"
CF_GID=$(id -g "${CF_USER}")
info "cloudflared GID: ${CF_GID}"
# Apply immediately
echo "${PING_GROUP_RANGE}" > /proc/sys/net/ipv4/ping_group_range
# Persist across reboots
SYSCTL_CONF="/etc/sysctl.d/99-cloudflared.conf"
cat > "${SYSCTL_CONF}" <<EOF
# Allow cloudflared ICMP proxy
net.ipv4.ping_group_range = ${PING_GROUP_RANGE}
EOF
sysctl -p "${SYSCTL_CONF}" > /dev/null
info "ping_group_range set to: ${PING_GROUP_RANGE} (persistent) ✓"
# --------------------------------------------------------------------------- #
# Install tunnel as systemd service
# --------------------------------------------------------------------------- #
section "Installing Tunnel Service"
info "Registering tunnel with token..."
cloudflared service install "${TUNNEL_TOKEN}"
# Override service to run as cloudflared user (not root)
OVERRIDE_DIR="/etc/systemd/system/cloudflared.service.d"
mkdir -p "${OVERRIDE_DIR}"
cat > "${OVERRIDE_DIR}/user.conf" <<EOF
[Service]
User=${CF_USER}
Group=${CF_GROUP}
EOF
info "Systemd override written: User=${CF_USER} Group=${CF_GROUP} ✓"
# --------------------------------------------------------------------------- #
# Fix credential file permissions
# --------------------------------------------------------------------------- #
section "Fixing Permissions"
if [[ -d /etc/cloudflared ]]; then
chown -R "${CF_USER}:${CF_GROUP}" /etc/cloudflared
chmod 750 /etc/cloudflared
find /etc/cloudflared -type f -exec chmod 640 {} \;
info "Permissions set on /etc/cloudflared ✓"
fi
# --------------------------------------------------------------------------- #
# Enable & start service
# --------------------------------------------------------------------------- #
section "Starting Service"
systemctl daemon-reload
systemctl enable cloudflared
systemctl restart cloudflared
info "Waiting for tunnel to connect (10s)..."
sleep 10
# --------------------------------------------------------------------------- #
# Verify
# --------------------------------------------------------------------------- #
section "Verification"
STATUS=$(systemctl is-active cloudflared)
if [[ "${STATUS}" == "active" ]]; then
info "Service status: ${STATUS} ✓"
else
warn "Service status: ${STATUS} — ดู log ด้านล่าง"
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Recent Logs"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
journalctl -u cloudflared --no-pager -n 20
echo ""
CONN_COUNT=$(journalctl -u cloudflared --no-pager -n 30 \
| grep -c "Connection established" || true)
if [[ "${CONN_COUNT}" -ge 1 ]]; then
echo -e "\e[32m✅ Tunnel connected! (${CONN_COUNT}/4 connections established)\e[0m"
else
echo -e "\e[33m⚠️ Connection not confirmed yet — ตรวจ log ด้านบนครับ\e[0m"
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Summary"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " cloudflared user : ${CF_USER} (GID: ${CF_GID})"
echo " ping_group_range : ${PING_GROUP_RANGE}"
echo " sysctl config : ${SYSCTL_CONF}"
echo " service override : ${OVERRIDE_DIR}/user.conf"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
info "Done! 🎉"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment