Hi zusammen,
falls jemand, wie wir, keinen Reverse-Proxy (http, tcp, udp) vor die 3CX schalten will, für den wollte ich kurz meine Lösung zeigen um trotzdem an LetsEncrypt Zertifikate zu kommen und diese automtisch auf dem 3CX server zu erneuern. Mir war es wichtig, möglichst wenig auf dem 3CX Server anzupassen, aber ganz ohne Anpassung klappt es leider nicht.
Der Text dient auch mir zur Dokumentation.
1. .well-known weiterleiten, damit LetsEncrypt die Validierung über einen externen Server durchführen kann
2. Externen ACME Server vorbereiten. Ich nutze Docker mit dem nging-proxy und acme-companion. Da könnt ihr auch etwas anders nutzen.
Ich habe die Container getrennt, man könnte die container auch in eine compose schreiben
Nun sollten die Zertifikate erstellt werden. Der nächste Schritt ist, ein Shell-Script zu schreiben, welches prüft ob neue Zertifikate vorhanden sind und diese auf den 3CX Server zu kopieren und nginx neu zu laden.
3. Upload und Deployment der Zertifikate.
Damit der Upload funktioniert wird vorher noch ein SSH Schlüsselpaar erstellt werden und auf dem 3CX Server eingetragen werden.
Nun kann auf dem ACME Server das Skript getestet werden.
4. Cronjob erstellen.
falls jemand, wie wir, keinen Reverse-Proxy (http, tcp, udp) vor die 3CX schalten will, für den wollte ich kurz meine Lösung zeigen um trotzdem an LetsEncrypt Zertifikate zu kommen und diese automtisch auf dem 3CX server zu erneuern. Mir war es wichtig, möglichst wenig auf dem 3CX Server anzupassen, aber ganz ohne Anpassung klappt es leider nicht.
Der Text dient auch mir zur Dokumentation.
1. .well-known weiterleiten, damit LetsEncrypt die Validierung über einen externen Server durchführen kann
Der Text zwischen den # muss VOR der Prüfung "http" eingefügt werden.
nano /var/lib/3cxpbx/Bin/nginx/conf/nginx.conf
nano /var/lib/3cxpbx/Bin/nginx/conf/nginx.conf
Code:
access_log off;
error_log /dev/null crit;
# --- Letsencrypt Proxy bypass http-block
if ($request_uri ~ "^/\.well-known/acme-challenge/") {
set $is_remote "";
}
# --- END: Letsencrypt Proxy bypass http-block
if ($scheme = 'http'){
set $block_remote "1";
}
nano /var/lib/3cxpbx/Bin/nginx/conf/60-letsencrypt-proxy.conf
Code:
location ^~ /.well-known/acme-challenge/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://acmeserver.domain.com/.well-known/acme-challenge/;
}
2. Externen ACME Server vorbereiten. Ich nutze Docker mit dem nging-proxy und acme-companion. Da könnt ihr auch etwas anders nutzen.
Code:
services:
proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
networks:
- proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./conf:/etc/nginx/conf.d
- ./vhost:/etc/nginx/vhost.d
- ./certs:/etc/nginx/certs:ro
- ./html:/usr/share/nginx/html
- /var/run/docker.sock:/tmp/docker.sock:ro
restart: unless-stopped
acme-companion:
image: nginxproxy/acme-companion
container_name: proxy-acme
networks:
- proxy
environment:
- [email protected]
volumes_from:
- proxy
volumes:
- ./certs:/etc/nginx/certs:rw
- ./acme:/etc/acme.sh
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
networks:
proxy:
external: true
Code:
services:
leproxy:
image: nginx
ports:
- 80
networks:
- proxy
environment:
- VIRTUAL_HOST=acmeserver.domain.com,3cxfqdn.domain.com
- HTTPS_METHOD=noredirect
- LETSENCRYPT_HOST=3cxfqdn.domain.com
restart: unless-stopped
networks:
proxy:
external: true
Nun sollten die Zertifikate erstellt werden. Der nächste Schritt ist, ein Shell-Script zu schreiben, welches prüft ob neue Zertifikate vorhanden sind und diese auf den 3CX Server zu kopieren und nginx neu zu laden.
3. Upload und Deployment der Zertifikate.
Pfade und Domain evtl anpassen
Bash:
#!/bin/bash
set -euo pipefail
DOMAIN="3cxfqdn.domain.com"
SRC_DIR="/opt/docker/proxy/certs/${DOMAIN}"
CACHE_DIR="/opt/docker/3cxcert/${DOMAIN}"
WORK_DIR="${CACHE_DIR}/work"
STATE_DIR="${CACHE_DIR}/state"
CURRENT_DIR="${CACHE_DIR}/current"
DST_HOST="[B]3cxfqdn.domain.com[/B]"
DST_DIR="/root/cert-import/${DOMAIN}"
SSH_DEPLOY_KEY="/root/.ssh/3cx_cert_deploy"
SSH_UPLOAD_KEY="/root/.ssh/3cx_cert_upload"
FULLCHAIN_SRC="${SRC_DIR}/fullchain.pem"
PRIVKEY_SRC="${SRC_DIR}/key.pem"
mkdir -p "$WORK_DIR" "$STATE_DIR" "$CURRENT_DIR"
# --- 1. Quelle prüfen ---
if [[ ! -f "$FULLCHAIN_SRC" || ! -f "$PRIVKEY_SRC" ]]; then
echo "[ERROR] Zertifikatsdateien nicht gefunden"
exit 1
fi
# --- 2. in work kopieren ---
cp "$FULLCHAIN_SRC" "$WORK_DIR/fullchain.pem"
cp "$PRIVKEY_SRC" "$WORK_DIR/privkey.pem"
# --- 3. Hash berechnen ---
sha256sum "$WORK_DIR/fullchain.pem" "$WORK_DIR/privkey.pem" > "$WORK_DIR/new.sha256"
# --- 4. Vergleich ---
if [[ -f "$STATE_DIR/deployed.sha256" ]]; then
if cmp -s "$WORK_DIR/new.sha256" "$STATE_DIR/deployed.sha256"; then
echo "[INFO] Zertifikat unverändert – nichts zu tun"
exit 0
fi
fi
echo "[INFO] Neues Zertifikat erkannt – starte Deploy"
# --- 5. Upload ---
scp -4 -i "$SSH_UPLOAD_KEY" \
"$WORK_DIR/fullchain.pem" \
"$WORK_DIR/privkey.pem" \
root@"$DST_HOST":"$DST_DIR"/
# --- 6. Remote Deploy triggern ---
ssh -i "$SSH_DEPLOY_KEY" root@"$DST_HOST" -4
echo "[INFO] Deploy erfolgreich – aktualisiere lokalen Cache"
# --- 7. Cache aktualisieren ---
cp "$WORK_DIR/fullchain.pem" "$CURRENT_DIR/fullchain.pem"
cp "$WORK_DIR/privkey.pem" "$CURRENT_DIR/privkey.pem"
cp "$WORK_DIR/new.sha256" "$STATE_DIR/deployed.sha256"
echo "[OK] Zertifikat erfolgreich synchronisiert"
Code:
ssh-keygen -t ed25519 -f /root/.ssh/3cx_cert_upload -N ""
ssh-keygen -t ed25519 -f /root/.ssh/3cx_cert_deploy -N ""
cat /root/.ssh/3cx_cert_upload.pub
cat /root/.ssh/3cx_cert_deploy.pub
Der erste Eintrag ist für den deploy Schlüssel, der zweite ist für den Upload Schlüssel
nano /root/.ssh/authorized_keys
nano /root/.ssh/authorized_keys
Code:
from="IP-ACME-Server",command="/usr/local/sbin/deploy-3cx-cert.sh 3cxfqdn.domain.com",no-port-forwarding,no-X11-forwardinwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA....
from="IP-ACME-Server",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA....
nano /usr/local/sbin/deploy-3cx-cert.sh
chmod 700 /usr/local/sbin/deploy-3cx-cert.sh
Bash:
#!/bin/bash
set -euo pipefail
DOMAIN="$1"
IMPORT_DIR="/root/cert-import/${DOMAIN}"
FULLCHAIN="${IMPORT_DIR}/fullchain.pem"
PRIVKEY="${IMPORT_DIR}/privkey.pem"
TARGET_DIR="/var/lib/3cxpbx/Bin/nginx/conf/Instance1"
TARGET_CERT="${TARGET_DIR}/domain_cert_${DOMAIN}.pem"
TARGET_KEY="${TARGET_DIR}/domain_key_${DOMAIN}.pem"
BACKUP_DIR="${TARGET_DIR}/backup-$(date +%F-%H%M%S)"
log() {
echo "[deploy-3cx-cert] $1"
}
if [[ ! -f "$FULLCHAIN" ]]; then
log "FEHLER: fullchain.pem nicht gefunden: $FULLCHAIN"
exit 1
fi
if [[ ! -f "$PRIVKEY" ]]; then
log "FEHLER: privkey.pem nicht gefunden: $PRIVKEY"
exit 1
fi
CERT_MOD="$(openssl x509 -noout -modulus -in "$FULLCHAIN" | openssl md5)"
KEY_MOD="$(openssl rsa -noout -modulus -in "$PRIVKEY" | openssl md5)"
if [[ "$CERT_MOD" != "$KEY_MOD" ]]; then
log "FEHLER: Zertifikat und Key passen nicht zusammen"
exit 1
fi
mkdir -p "$BACKUP_DIR"
if [[ -f "$TARGET_CERT" ]]; then
cp -a "$TARGET_CERT" "$BACKUP_DIR/"
fi
if [[ -f "$TARGET_KEY" ]]; then
cp -a "$TARGET_KEY" "$BACKUP_DIR/"
fi
cp "$FULLCHAIN" "${TARGET_CERT}.new"
cp "$PRIVKEY" "${TARGET_KEY}.new"
chmod 600 "${TARGET_CERT}.new" "${TARGET_KEY}.new"
chown root:root "${TARGET_CERT}.new" "${TARGET_KEY}.new"
mv "${TARGET_CERT}.new" "$TARGET_CERT"
mv "${TARGET_KEY}.new" "$TARGET_KEY"
if nginx -t; then
nginx -s reload
log "ERFOLG: Zertifikat für ${DOMAIN} aktualisiert und nginx neu geladen"
else
log "FEHLER: nginx -t fehlgeschlagen, versuche Rollback"
if [[ -f "${BACKUP_DIR}/$(basename "$TARGET_CERT")" ]]; then
cp -a "${BACKUP_DIR}/$(basename "$TARGET_CERT")" "$TARGET_CERT"
fi
if [[ -f "${BACKUP_DIR}/$(basename "$TARGET_KEY")" ]]; then
cp -a "${BACKUP_DIR}/$(basename "$TARGET_KEY")" "$TARGET_KEY"
fi
nginx -t && nginx -s reload || true
exit 1
fi
Nun kann auf dem ACME Server das Skript getestet werden.
4. Cronjob erstellen.
Zuletzt bearbeitet: