feat(haproxy): Add webroot mode for ACME - no HAProxy restart needed

Certificate issuance now uses webroot mode instead of standalone:
- HAProxy routes /.well-known/acme-challenge/ to local ACME webserver
- Added acme_challenge backend on port 8402
- Uses busybox httpd to serve challenge files
- No HAProxy restart required during certificate requests
- Config auto-regenerates before cert request to ensure ACME backend

This eliminates downtime during certificate issuance and allows
multiple concurrent certificate requests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-28 06:45:08 +01:00
parent bc5bd8d8ce
commit 67cd2854a1

View File

@ -409,6 +409,11 @@ _generate_frontends() {
frontend http-in
bind *:$http_port
mode http
# ACME challenge routing (no HAProxy restart needed for cert issuance)
acl is_acme_challenge path_beg /.well-known/acme-challenge/
use_backend acme_challenge if is_acme_challenge
EOF
# Add HTTPS redirect rules for vhosts with ssl_redirect
@ -484,6 +489,16 @@ _add_vhost_acl() {
_generate_backends() {
config_load haproxy
# ACME challenge backend (for certificate issuance without HAProxy restart)
# Serves /.well-known/acme-challenge/ from /var/www/acme-challenge/
cat << EOF
backend acme_challenge
mode http
server acme_webroot 192.168.255.1:8402 check
EOF
# Track which backends are generated
_generated_backends=""
@ -770,6 +785,10 @@ cmd_cert_add() {
local domain="$1"
[ -z "$domain" ] && { log_error "Domain required"; return 1; }
# Ensure HAProxy config has ACME backend (for webroot mode)
log_info "Ensuring HAProxy config is up to date..."
generate_config
local email=$(uci_get acme.email)
local staging=$(uci_get acme.staging)
local key_type_raw=$(uci_get acme.key_type) || key_type_raw="ec-256"
@ -820,21 +839,34 @@ cmd_cert_add() {
"$ACME_SH" --register-account -m "$email" --server letsencrypt $staging_flag --home "$LE_WORKING_DIR" || true
fi
# Check if HAProxy is using the port
local haproxy_was_running=0
if lxc_running; then
log_info "Temporarily stopping HAProxy for certificate issuance..."
haproxy_was_running=1
/etc/init.d/haproxy stop 2>/dev/null || true
# Setup webroot for ACME challenges (HAProxy routes /.well-known/acme-challenge/ here)
local ACME_WEBROOT="/var/www/acme-challenge"
ensure_dir "$ACME_WEBROOT/.well-known/acme-challenge"
chmod 755 "$ACME_WEBROOT" "$ACME_WEBROOT/.well-known" "$ACME_WEBROOT/.well-known/acme-challenge"
# Start simple webserver for ACME challenges (if not already running)
local ACME_PORT=8402
if ! netstat -tln 2>/dev/null | grep -q ":$ACME_PORT "; then
log_info "Starting ACME challenge webserver on port $ACME_PORT..."
# Use busybox httpd (available on OpenWrt)
start-stop-daemon -S -b -x /usr/sbin/httpd -- -p $ACME_PORT -h "$ACME_WEBROOT" -f 2>/dev/null || \
busybox httpd -p $ACME_PORT -h "$ACME_WEBROOT" &
sleep 1
fi
# Ensure HAProxy is running with ACME backend
if ! lxc_running; then
log_info "Starting HAProxy..."
/etc/init.d/haproxy start 2>/dev/null || true
sleep 2
fi
# Issue certificate using standalone mode
log_info "Issuing certificate (standalone mode on port $http_port)..."
# Issue certificate using webroot mode (NO HAProxy restart needed!)
log_info "Issuing certificate (webroot mode - HAProxy stays running)..."
local acme_result=0
"$ACME_SH" --issue -d "$domain" \
--server letsencrypt \
--standalone --httpport "$http_port" \
--webroot "$ACME_WEBROOT" \
--keylength "$key_type" \
$staging_flag \
--home "$LE_WORKING_DIR" || acme_result=$?
@ -856,19 +888,19 @@ cmd_cert_add() {
chmod 600 "$CERTS_PATH/$domain.pem"
# Clean up intermediate files - HAProxy only needs the .pem file
# Keeping these causes issues when HAProxy loads certs from directory
rm -f "$CERTS_PATH/$domain.crt" "$CERTS_PATH/$domain.key" "$CERTS_PATH/$domain.fullchain.pem" "$CERTS_PATH/$domain.crt.key" 2>/dev/null
fi
# Restart HAProxy if it was running
if [ "$haproxy_was_running" = "1" ]; then
log_info "Restarting HAProxy..."
/etc/init.d/haproxy start 2>/dev/null || true
# Reload HAProxy to pick up new cert
log_info "Reloading HAProxy to use new certificate..."
/etc/init.d/haproxy reload 2>/dev/null || true
fi
# Check if certificate was created
if [ ! -f "$CERTS_PATH/$domain.pem" ]; then
log_error "Certificate issuance failed. Ensure port $http_port is accessible from internet and domain points to this IP."
log_error "Certificate issuance failed. Check:"
log_error " 1. Domain $domain points to this server's public IP"
log_error " 2. Port 80 is accessible from internet"
log_error " 3. HAProxy is running with ACME backend (haproxyctl generate)"
return 1
fi
log_info "Certificate ready: $CERTS_PATH/$domain.pem"