Организация собственного HTTPS-прокси остаётся востребованной задачей — как для обеспечения контролируемого доступа к внешним ресурсам, так и для изоляции сетевого трафика. Ниже приведён практический подход к развёртыванию такого решения на базе свободного ПО с использованием автоматического выпуска TLS-сертификатов.
Архитектура решения
Итоговая система строится по принципу разделения ответственности между компонентами:
- Squid выполняет роль классического HTTP-прокси и обрабатывает авторизацию пользователей;
- stunnel обеспечивает TLS-шифрование и принимает входящие HTTPS-подключения;
- Certbot автоматически получает и обновляет сертификаты от Let’s Encrypt;
- утилита
htpasswdиспользуется для управления учётными данными.
Трафик клиента проходит следующую цепочку:
Клиент → HTTPS (8443)
↓
stunnel (TLS termination)
↓
Squid (127.0.0.1:3128)
↓
Интернет
Таким образом:
- внешний доступ осуществляется только через TLS;
- сам прокси-движок изолирован и недоступен извне;
- авторизация реализуется на уровне Squid.
Доменное имя и сертификаты
Для выпуска сертификата требуется доменное имя. В случаях, когда у администратора отсутствует собственный домен, можно воспользоваться бесплатным сервисом динамического DNS — DuckDNS.
Он позволяет:
- быстро зарегистрировать поддомен вида
example.duckdns.org; - автоматически обновлять IP-адрес;
- использовать его для выпуска сертификатов через Let’s Encrypt.
Это особенно актуально для:
- домашних серверов;
- облачных инстансов без закреплённого домена;
- временных инфраструктур.
Совместимость с актуальными дистрибутивами
Скрипт ниже адаптирован для:
- Debian 12 (Bookworm)
- Debian 13 (Trixie)
- Ubuntu 22.04 LTS
- Ubuntu 24.04 LTS
Скрипт автоматического развёртывания
Как использовать:
chmod +x setup-proxy.sh
sudo ./setup-proxy.sh proxy.example.com your@email.com
Код самого скрипта:
#!/usr/bin/env bash
set -Eeuo pipefail
DOMAIN="${1:-}"
EMAIL="${2:-}"
if [[ -z "$DOMAIN" || -z "$EMAIL" ]]; then
echo "Usage: $0 <domain> <email>"
exit 1
fi
PASSFILE="/etc/squid/passwd"
STUNNEL_PEM="/etc/stunnel/stunnel.pem"
STUNNEL_CONF="/etc/stunnel/stunnel.conf"
STUNNEL_PID="/var/lib/stunnel4/stunnel.pid"
RENEW_HOOK="/etc/letsencrypt/renewal-hooks/deploy/stunnel-reload.sh"
log() {
echo "[$1] $2"
}
require_root() {
if [[ "${EUID}" -ne 0 ]]; then
echo "Run this script as root"
exit 1
fi
}
detect_auth_bin() {
local candidates=(
/usr/lib/squid/basic_ncsa_auth
/usr/libexec/squid/basic_ncsa_auth
/usr/lib64/squid/basic_ncsa_auth
)
local bin
for bin in "${candidates[@]}"; do
if [[ -x "$bin" ]]; then
echo "$bin"
return 0
fi
done
echo "Cannot find basic_ncsa_auth binary" >&2
exit 1
}
generate_password() {
local pass=""
while [[ ${#pass} -lt 56 ]]; do
pass="$(openssl rand -base64 64 | tr -d '\n' | tr -dc 'A-Za-z0-9' | head -c 56)"
done
printf '%s\n' "$pass"
}
ensure_passfile_owner() {
if id proxy >/dev/null 2>&1; then
chown proxy:proxy "$PASSFILE"
else
chown root:root "$PASSFILE"
fi
}
write_squid_config() {
local auth_bin="$1"
cat > /etc/squid/squid.conf <<EOF
auth_param basic program $auth_bin $PASSFILE
auth_param basic realm proxy
auth_param basic credentialsttl 2 hours
acl authenticated proxy_auth REQUIRED
acl SSL_ports port 443
acl CONNECT method CONNECT
http_port 127.0.0.1:3128
http_access allow authenticated
http_access deny all
http_access allow CONNECT SSL_ports
http_access deny CONNECT !SSL_ports
via off
forwarded_for delete
access_log /var/log/squid/access.log
cache_log /var/log/squid/cache.log
pid_filename /run/squid.pid
EOF
}
write_stunnel_config() {
mkdir -p /var/lib/stunnel4
chown root:root /var/lib/stunnel4
chmod 755 /var/lib/stunnel4
cat > "$STUNNEL_CONF" <<EOF
foreground = no
pid = $STUNNEL_PID
[https-proxy]
accept = 0.0.0.0:8443
connect = 127.0.0.1:3128
cert = $STUNNEL_PEM
EOF
}
write_renew_hook() {
mkdir -p "$(dirname "$RENEW_HOOK")"
cat > "$RENEW_HOOK" <<EOF
#!/usr/bin/env bash
set -Eeuo pipefail
CERT="/etc/letsencrypt/live/$DOMAIN/fullchain.pem"
KEY="/etc/letsencrypt/live/$DOMAIN/privkey.pem"
PEM="$STUNNEL_PEM"
cat "\$CERT" "\$KEY" > "\$PEM"
chmod 600 "\$PEM"
systemctl restart stunnel4
EOF
chmod +x "$RENEW_HOOK"
}
obtain_or_refresh_certificate() {
local cert="/etc/letsencrypt/live/$DOMAIN/fullchain.pem"
local key="/etc/letsencrypt/live/$DOMAIN/privkey.pem"
systemctl stop stunnel4 >/dev/null 2>&1 || true
if [[ -f "$cert" && -f "$key" ]]; then
certbot certonly --standalone --keep-until-expiring -d "$DOMAIN" --agree-tos -m "$EMAIL" --non-interactive
else
certbot certonly --standalone -d "$DOMAIN" --agree-tos -m "$EMAIL" --non-interactive
fi
if [[ ! -f "$cert" || ! -f "$key" ]]; then
echo "Certificate files were not created: $cert / $key" >&2
exit 1
fi
cat "$cert" "$key" > "$STUNNEL_PEM"
chmod 600 "$STUNNEL_PEM"
}
enable_stunnel() {
if [[ -f /etc/default/stunnel4 ]]; then
if grep -q '^ENABLED=' /etc/default/stunnel4; then
sed -i 's/^ENABLED=.*/ENABLED=1/' /etc/default/stunnel4
else
echo 'ENABLED=1' >> /etc/default/stunnel4
fi
fi
}
free_port_8443_if_needed() {
local pids=""
pids="$(ss -ltnp '( sport = :8443 )' 2>/dev/null | awk 'NR>1 {print $NF}' | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | sort -u)"
if [[ -n "$pids" ]]; then
echo "Port 8443 is already in use by PID(s): $pids"
echo "Stopping stunnel4 service and killing stale listeners on 8443..."
systemctl stop stunnel4 >/dev/null 2>&1 || true
for pid in $pids; do
kill "$pid" >/dev/null 2>&1 || true
done
sleep 1
fi
}
show_port_8443_listener() {
echo
echo "Listener on 8443:"
ss -ltnp '( sport = :8443 )' || true
}
main() {
require_root
log "1/9" "Installing packages..."
apt update
DEBIAN_FRONTEND=noninteractive apt install -y \
squid \
apache2-utils \
certbot \
stunnel4 \
openssl \
iproute2
log "2/9" "Generating users..."
rm -f "$PASSFILE"
declare -A CREDS
for i in 1 2 3 4 5; do
user="user$i"
pass="$(generate_password)"
CREDS["$user"]="$pass"
if [[ "$i" -eq 1 ]]; then
htpasswd -b -c "$PASSFILE" "$user" "$pass"
else
htpasswd -b "$PASSFILE" "$user" "$pass"
fi
done
chmod 640 "$PASSFILE"
ensure_passfile_owner
log "3/9" "Configuring Squid..."
AUTH_BIN="$(detect_auth_bin)"
write_squid_config "$AUTH_BIN"
systemctl enable squid
systemctl restart squid
log "4/9" "Obtaining certificate..."
obtain_or_refresh_certificate
log "5/9" "Configuring stunnel..."
write_stunnel_config
enable_stunnel
log "6/9" "Freeing TCP port 8443 if needed..."
free_port_8443_if_needed
log "7/9" "Starting stunnel..."
systemctl enable stunnel4
systemctl restart stunnel4
log "8/9" "Setting up certificate renewal hook..."
write_renew_hook
log "9/9" "Checking services..."
systemctl --no-pager --full status squid stunnel4 >/dev/null
show_port_8443_listener
echo
echo "====== USERS ======"
for user in user1 user2 user3 user4 user5; do
echo "$user : ${CREDS[$user]}"
done
echo
echo "Proxy endpoint:"
echo "https://<user>:<pass>@$DOMAIN:8443"
}
main "$@"
Эксплуатационные особенности
Решение не требует ручного сопровождения после установки:
- сертификаты обновляются автоматически через systemd-таймер Certbot;
- при обновлении выполняется hook, пересобирающий PEM-файл и перезапускающий stunnel;
- Squid не требует перезапуска, так как не работает с TLS напрямую.
Заключение
Представленная схема демонстрирует практичный и воспроизводимый способ развёртывания защищённого прокси-сервиса с минимальными зависимостями. Использование Let’s Encrypt и DuckDNS позволяет отказаться от платных сертификатов и доменной инфраструктуры, сохраняя при этом промышленный уровень безопасности.
Подход масштабируется, легко автоматизируется и может служить базой для более сложных сетевых решений — включая фильтрацию трафика, аудит и контроль доступа.
PS. Скрипт протестирован на Debain 12.
Перемещено hobbit из admin






