Certificatul nou nu va suprascrie direct fișierele folosite de Nginx. Îl vom copia într-o versiune separată, îi vom verifica cheia, numele DNS și perioada de valabilitate, apoi vom schimba atomic o singură legătură simbolică. Nginx este reîncărcat numai dacă noua configurație trece testul.
Scenariul este un server web Ubuntu care găzduiește app.example.net. Domeniul este un exemplu rezervat documentației: înainte de emiterea certificatului trebuie înlocuit peste tot cu un domeniu real, controlat de administrator și direcționat prin DNS către server.
La final vor funcționa împreună:
- Certbot, cu validare HTTP-01 prin directorul webroot;
- un deploy hook executat numai după obținerea sau reînnoirea cu succes;
- directoare versionate în
/etc/nginx/tls-releases/; - legătura activă
/etc/nginx/tls-current/app.example.net; - validarea certificatului și a cheii înainte de deploy;
- testarea configurației și reîncărcarea controlată a Nginx;
- un serviciu și un timer
systemdcare verifică zilnic certificatul local și certificatul servit pe portul 443; - o alertă în journal dacă mai sunt sub 30 de zile sau serverul livrează alt certificat.
Pentru administrarea obișnuită a serviciului și interpretarea stărilor active, inactive sau failed, este util și ghidul despre verificarea serviciilor Linux cu systemctl.
Fixăm domeniul, versiunile și condițiile de emitere
Laboratorul folosește Ubuntu Server 24.04 LTS, Nginx 1.24, Certbot 2.9, OpenSSL 3.0 și systemd 255, adică versiunile de bază din Ubuntu 24.04. Reviziile de securitate afișate după numărul principal pot fi diferite. Nu instala versiuni mai vechi pentru a reproduce textual output-ul; păstrează pachetele actualizate din depozitele distribuției.
Sunt suficiente un vCPU, 1 GB RAM și aproximativ 1 GB liber. Configurarea durează 30–60 de minute. Emiterea cere suplimentar:
- un domeniu real controlat de tine;
- înregistrarea DNS a domeniului către adresa publică a serverului;
- acces TCP din internet la portul 80 pentru challenge-ul HTTP-01;
- acces TCP la portul 443 pentru HTTPS;
- lipsa unui proxy care interceptează calea
/.well-known/acme-challenge/.
Verifică mediul înainte de modificări:
cat /etc/os-release
uname -m
free -h
df -h /
nginx -v
certbot --version
openssl version
systemctl --version | head -n 1
systemctl is-system-running
Un rezultat orientativ este:
PRETTY_NAME="Ubuntu 24.04.x LTS"
x86_64
nginx version: nginx/1.24.0
certbot 2.9.0
OpenSSL 3.0.x
systemd 255 (...)
running
Valorile x, reviziile Ubuntu și sufixele pachetelor pot diferi. Proiectul presupune Nginx 1.24 sau mai nou, Certbot 2.9 sau mai nou și OpenSSL 3.0, deoarece scriptul folosește openssl x509 -checkhost.
Instalează într-un singur bloc pachetele necesare:
sudo apt update
sudo apt install --yes nginx certbot openssl ca-certificates
sudo systemctl enable --now nginx
Dacă serverul găzduiește deja mai multe site-uri, verifică înainte cum sunt separate configurațiile. Articolul despre virtual hosts pe un server Linux explică structura folosită de Nginx pentru fiecare domeniu.
Șase fișiere leagă emiterea, activarea și supravegherea
Proiectul va avea următoarea structură:
/etc/default/acme-atomic-deploy
└── domeniul administrat și pragul de expirare
/var/www/acme/
└── rădăcina publică folosită numai pentru challenge-ul HTTP-01
/etc/letsencrypt/renewal-hooks/deploy/50-nginx-atomic
└── verifică și activează certificatul după o reînnoire reușită
/usr/local/sbin/check-acme-certificate
└── compară certificatul activ cu cel servit de Nginx
/etc/systemd/system/acme-certificate-check.service
└── rulează verificarea cu drepturi și acces limitate
/etc/systemd/system/acme-certificate-check.timer
└── pornește verificarea zilnică
/etc/nginx/sites-available/app.example.net
└── publică challenge-ul și serviciul HTTPS
/etc/nginx/tls-releases/app.example.net/
├── 20260926T120000Z-0123ABCD/
│ ├── fullchain.pem
│ └── privkey.pem
└── alte versiuni validate
/etc/nginx/tls-current/app.example.net
└── legătură simbolică schimbată atomic către versiunea activă
tls-releases păstrează maximum trei versiuni. Nginx citește certificatul prin tls-current, nu direct din /etc/letsencrypt/live. Astfel, fișierele care formează perechea certificat–cheie sunt pregătite complet înainte ca legătura activă să fie schimbată.
Pasul 1 — Publicăm numai calea ACME pe portul 80
De ce: serverul ACME trebuie să poată prelua fișierul temporar creat de Certbot. Configurația inițială nu activează încă TLS, deoarece certificatul nu există.
Înlocuiește app.example.net cu domeniul real în toate fișierele și comenzile tutorialului. Configurează și o adresă de e-mail administrativă reală.
Creează fișierul /etc/default/acme-atomic-deploy:
sudo install -d -m 0755 /var/www/acme/.well-known/acme-challenge
sudo install -d -m 0755 /etc/nginx/tls-releases /etc/nginx/tls-current
sudo tee /etc/default/acme-atomic-deploy >/dev/null <<'EOF'
DOMAIN=app.example.net
EMAIL=admin@example.net
EXPIRY_SECONDS=2592000
EOF
sudo chmod 0644 /etc/default/acme-atomic-deploy
Înainte de a înlocui o configurație Nginx existentă, salveaz-o:
if sudo test -e /etc/nginx/sites-available/app.example.net; then
sudo cp -a /etc/nginx/sites-available/app.example.net
/etc/nginx/sites-available/app.example.net.bak
fi
Creează integral /etc/nginx/sites-available/app.example.net:
server {
listen 80;
listen [::]:80;
server_name app.example.net;
location ^~ /.well-known/acme-challenge/ {
root /var/www/acme;
default_type text/plain;
try_files $uri =404;
}
location / {
default_type text/plain;
return 200 "Serviciul HTTP este pregătit pentru ACME.n";
}
}
Activează site-ul și verifică sintaxa:
sudo ln -sfn /etc/nginx/sites-available/app.example.net
/etc/nginx/sites-enabled/app.example.net
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
printf '%sn' 'acme-webroot-ok' |
sudo tee /var/www/acme/.well-known/acme-challenge/control >/dev/null
curl --fail --silent
http://app.example.net/.well-known/acme-challenge/control
^~ acordă prioritate locației ACME. try_files $uri =404 refuză cererile pentru fișiere inexistente, iar root /var/www/acme face ca tokenul să fie citit din directorul creat anterior.
Output-ul așteptat este:
nginx: configuration file /etc/nginx/nginx.conf test is successful
acme-webroot-ok
Punct de control: dacă testul cu curl nu returnează exact acme-webroot-ok, nu cere certificatul; verifică DNS-ul, firewall-ul, portul 80 și journalctl -u nginx.
Pasul 2 — Obținem certificatul fără ca Certbot să rescrie Nginx
De ce: modul certonly --webroot obține certificatul, dar păstrează administrarea configurației Nginx în proiectul nostru. Această separare permite validarea și deploy-ul atomic.
Încarcă valorile și cere certificatul:
set -euo pipefail
. /etc/default/acme-atomic-deploy
sudo certbot certonly
--webroot
--webroot-path /var/www/acme
--domain "$DOMAIN"
--cert-name "$DOMAIN"
--email "$EMAIL"
--agree-tos
--non-interactive
--webroot-path indică directorul servit pentru HTTP-01. --cert-name oferă lineage-ului un nume stabil, folosit ulterior în /etc/letsencrypt/live/$DOMAIN. Opțiunea --non-interactive împiedică blocarea automatizării în așteptarea unei întrebări.
Un rezultat reușit conține linii similare cu:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/app.example.net/fullchain.pem
Key is saved at: /etc/letsencrypt/live/app.example.net/privkey.pem
Căile trebuie să existe:
sudo test -r "/etc/letsencrypt/live/$DOMAIN/fullchain.pem"
sudo test -r "/etc/letsencrypt/live/$DOMAIN/privkey.pem"
sudo certbot certificates
Nu copia cheia privată într-un director accesibil utilizatorilor obișnuiți și nu o include în backup-uri sau rapoarte fără criptare.
Punct de control: dacă Certbot raportează unauthorized, verifică dintr-o altă rețea URL-ul de control creat la pasul anterior; o rezolvare DNS locală nu dovedește accesibilitatea publică.
Pasul 3 — Construim hook-ul care verifică perechea și schimbă atomic versiunea
De ce: hook-ul rulează după o reînnoire reușită. El refuză un certificat pentru alt domeniu, o cheie nepotrivită, un certificat expirat sau o configurație Nginx invalidă.
Creează integral /etc/letsencrypt/renewal-hooks/deploy/50-nginx-atomic:
#!/usr/bin/env bash
set -euo pipefail
umask 077
readonly CONFIG=/etc/default/acme-atomic-deploy
readonly LOCK=/run/lock/acme-nginx-atomic.lock
fail() {
logger -p daemon.err -t acme-nginx-atomic -- "$*"
printf 'Eroare: %sn' "$*" >&2
exit 1
}
[[ -r "$CONFIG" ]] || fail "Lipsește $CONFIG"
# shellcheck disable=SC1090
. "$CONFIG"
: "${DOMAIN:?DOMAIN nu este definit}"
: "${RENEWED_LINEAGE:?RENEWED_LINEAGE nu este definit}"
: "${RENEWED_DOMAINS:?RENEWED_DOMAINS nu este definit}"
case " $RENEWED_DOMAINS " in
*" $DOMAIN "*) ;;
*)
logger -t acme-nginx-atomic --
"Certificatul pentru $RENEWED_DOMAINS nu aparține $DOMAIN; ignorat"
exit 0
;;
esac
exec 9>"$LOCK"
flock -n 9 || fail "Alt deploy este deja în curs"
readonly SOURCE_CERT="$RENEWED_LINEAGE/fullchain.pem"
readonly SOURCE_KEY="$RENEWED_LINEAGE/privkey.pem"
readonly RELEASE_ROOT="/etc/nginx/tls-releases/$DOMAIN"
readonly CURRENT="/etc/nginx/tls-current/$DOMAIN"
[[ -r "$SOURCE_CERT" ]] || fail "Nu pot citi $SOURCE_CERT"
[[ -r "$SOURCE_KEY" ]] || fail "Nu pot citi $SOURCE_KEY"
openssl x509 -in "$SOURCE_CERT" -noout -checkhost "$DOMAIN" >/dev/null ||
fail "Certificatul nu este valabil pentru $DOMAIN"
openssl x509 -in "$SOURCE_CERT" -checkend 3600 -noout ||
fail "Certificatul expiră în mai puțin de o oră"
cert_key_id="$(
openssl x509 -in "$SOURCE_CERT" -pubkey -noout |
openssl pkey -pubin -outform DER 2>/dev/null |
sha256sum | awk '{print $1}'
)"
private_key_id="$(
openssl pkey -in "$SOURCE_KEY" -pubout -outform DER 2>/dev/null |
sha256sum | awk '{print $1}'
)"
[[ "$cert_key_id" == "$private_key_id" ]] ||
fail "Cheia privată nu corespunde certificatului"
serial="$(
openssl x509 -in "$SOURCE_CERT" -noout -serial |
cut -d= -f2 | tr -cd '[:alnum:]'
)"
release_name="$(date -u +%Y%m%dT%H%M%SZ)-$serial"
install -d -m 0750 -o root -g www-data "$RELEASE_ROOT"
staging="$(mktemp -d "$RELEASE_ROOT/.staging.XXXXXX")"
next_link="${CURRENT}.next"
old_target=""
cleanup() {
rm -rf -- "$staging"
rm -f -- "$next_link"
}
trap cleanup EXIT
install -m 0644 -o root -g root
"$SOURCE_CERT" "$staging/fullchain.pem"
install -m 0640 -o root -g www-data
"$SOURCE_KEY" "$staging/privkey.pem"
openssl x509 -in "$staging/fullchain.pem" -noout >/dev/null
openssl pkey -in "$staging/privkey.pem" -noout >/dev/null 2>&1
release="$RELEASE_ROOT/$release_name"
mv -- "$staging" "$release"
if [[ -L "$CURRENT" ]]; then
old_target="$(readlink "$CURRENT")"
fi
ln -s "$release" "$next_link"
mv -Tf -- "$next_link" "$CURRENT"
rollback() {
if [[ -n "$old_target" ]]; then
ln -s "$old_target" "$next_link"
mv -Tf -- "$next_link" "$CURRENT"
else
rm -f -- "$CURRENT"
fi
}
if ! nginx -t; then
rollback
fail "nginx -t a eșuat; versiunea anterioară a fost restaurată"
fi
if ! systemctl reload nginx; then
rollback
nginx -t >/dev/null 2>&1 || true
systemctl reload nginx >/dev/null 2>&1 || true
fail "Reload-ul Nginx a eșuat; legătura anterioară a fost restaurată"
fi
mapfile -t releases < <(
find "$RELEASE_ROOT"
-mindepth 1 -maxdepth 1 -type d -name '20*'
-printf '%T@ %pn' |
sort -nr |
awk '{print $2}'
)
for ((index=3; index<${#releases[@]}; index++)); do
[[ "${releases[$index]}" == "$(readlink -f "$CURRENT")" ]] ||
rm -rf -- "${releases[$index]}"
done
logger -t acme-nginx-atomic --
"Activat $release pentru $DOMAIN și reîncărcat Nginx"
printf 'Activat: %sn' "$release"
Acordă permisiunea de execuție și rulează hook-ul manual pentru certificatul inițial:
sudo chmod 0750
/etc/letsencrypt/renewal-hooks/deploy/50-nginx-atomic
. /etc/default/acme-atomic-deploy
sudo env
RENEWED_LINEAGE="/etc/letsencrypt/live/$DOMAIN"
RENEWED_DOMAINS="$DOMAIN"
/etc/letsencrypt/renewal-hooks/deploy/50-nginx-atomic
sudo readlink -f "/etc/nginx/tls-current/$DOMAIN"
sudo find "/etc/nginx/tls-releases/$DOMAIN"
-maxdepth 2 -type f -printf '%M %u:%g %pn'
flock împiedică două deploy-uri simultane. Fișierele sunt mutate în același filesystem, iar mv -T înlocuiește legătura simbolică într-o singură operație de redenumire. Nginx continuă să folosească vechile file descriptors până la reload.
Output-ul orientativ este:
Activat: /etc/nginx/tls-releases/app.example.net/20260926T120000Z-0123ABCD
/etc/nginx/tls-releases/app.example.net/20260926T120000Z-0123ABCD
-rw-r--r-- root:root .../fullchain.pem
-rw-r----- root:www-data .../privkey.pem
Timestamp-ul și seria certificatului vor fi diferite.
Punct de control: dacă legătura tls-current nu apare, citește journalctl -t acme-nginx-atomic și nu activa încă blocul TLS.
Pasul 4 — Mutăm Nginx pe certificatul versionat
De ce: abia acum există o pereche certificat–cheie verificată. Configurația HTTPS va referi legătura atomică, nu lineage-ul administrat intern de Certbot.
Salvează din nou configurația HTTP:
sudo cp -a /etc/nginx/sites-available/app.example.net
/etc/nginx/sites-available/app.example.net.http-only.bak
Înlocuiește integral /etc/nginx/sites-available/app.example.net:
server {
listen 80;
listen [::]:80;
server_name app.example.net;
location ^~ /.well-known/acme-challenge/ {
root /var/www/acme;
default_type text/plain;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name app.example.net;
ssl_certificate
/etc/nginx/tls-current/app.example.net/fullchain.pem;
ssl_certificate_key
/etc/nginx/tls-current/app.example.net/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:TLS:10m;
ssl_session_timeout 10m;
location / {
default_type text/plain;
return 200 "HTTPS folosește certificatul activat atomic.n";
}
}
Testează înainte de reload:
sudo nginx -t
sudo systemctl reload nginx
systemctl is-active nginx
curl --fail --silent https://app.example.net/
openssl s_client
-connect app.example.net:443
-servername app.example.net
-verify_return_error </dev/null 2>/dev/null |
openssl x509 -noout -subject -issuer -dates
Rezultatul aplicației trebuie să fie:
HTTPS folosește certificatul activat atomic.
Comanda OpenSSL va afișa subiectul, emitentul, notBefore și notAfter ale certificatului real. Nu publica output-ul complet dacă numele infrastructurii este sensibil.
Punct de control: dacă nginx -t eșuează, nu executa reload; verifică legătura cu readlink -f și permisiunile fiecărei componente cu namei -l.
Pasul 5 — Legăm simularea Certbot de același deploy hook
De ce: o reînnoire automată nu este verificată doar prin existența timerului. Simularea trebuie să parcurgă validarea ACME și să confirme că hook-ul poate fi executat.
Verifică timerul instalat de pachet și rulează dry-run-ul:
systemctl list-timers --all certbot.timer
systemctl cat certbot.timer
. /etc/default/acme-atomic-deploy
sudo certbot renew
--cert-name "$DOMAIN"
--dry-run
--run-deploy-hooks
sudo journalctl
-t acme-nginx-atomic
--since '-15 minutes'
--no-pager
--dry-run folosește mediul ACME de test. --run-deploy-hooks cere explicit executarea hook-urilor și în simulare. Conform comportamentului documentat de Certbot, hook-ul primește certificatul activ, nu certificatul de test neverificat, pentru a nu instala în producție un lanț emis de staging.
Output-ul trebuie să includă un rezultat similar:
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/app.example.net/fullchain.pem (success)
În journal trebuie să apară mesajul Activat ... și reîncărcat Nginx.
Nu adăuga încă un cron pentru certbot renew dacă pachetul distribuției are deja certbot.timer. Două mecanisme independente pot crea execuții concurente inutile, chiar dacă hook-ul nostru folosește un lock.
Punct de control: dacă simularea reînnoirii reușește, dar mesajul hook-ului lipsește, verifică permisiunea de execuție și calea /etc/letsencrypt/renewal-hooks/deploy/.
Pasul 6 — Comparăm zilnic fișierul activ cu certificatul servit
De ce: un certificat nou pe disc nu dovedește că Nginx îl și livrează. Verificarea va controla expirarea certificatului local, conexiunea TLS locală și egalitatea amprentelor SHA-256.
Creează integral /usr/local/sbin/check-acme-certificate:
#!/usr/bin/env bash
set -euo pipefail
readonly CONFIG=/etc/default/acme-atomic-deploy
alert() {
logger -p daemon.alert -t acme-certificate-check -- "$*"
printf 'ALERTĂ: %sn' "$*" >&2
exit 1
}
[[ -r "$CONFIG" ]] || alert "Lipsește $CONFIG"
# shellcheck disable=SC1090
. "$CONFIG"
: "${DOMAIN:?DOMAIN nu este definit}"
: "${EXPIRY_SECONDS:?EXPIRY_SECONDS nu este definit}"
readonly LOCAL_CERT="/etc/nginx/tls-current/$DOMAIN/fullchain.pem"
[[ -r "$LOCAL_CERT" ]] || alert "Nu pot citi $LOCAL_CERT"
openssl x509
-in "$LOCAL_CERT"
-checkend "$EXPIRY_SECONDS"
-noout ||
alert "Certificatul local pentru $DOMAIN expiră în pragul configurat"
remote_cert="$(mktemp)"
trap 'rm -f "$remote_cert"' EXIT
if ! timeout 10 openssl s_client
-connect 127.0.0.1:443
-servername "$DOMAIN"
-showcerts </dev/null 2>/dev/null |
openssl x509 -outform PEM >"$remote_cert"; then
alert "Nu am putut prelua certificatul TLS servit pentru $DOMAIN"
fi
[[ -s "$remote_cert" ]] ||
alert "Nginx nu a returnat un certificat parsabil"
openssl x509
-in "$remote_cert"
-checkend "$EXPIRY_SECONDS"
-noout ||
alert "Certificatul servit pentru $DOMAIN expiră în pragul configurat"
local_fingerprint="$(
openssl x509 -in "$LOCAL_CERT" -noout -fingerprint -sha256 |
cut -d= -f2
)"
remote_fingerprint="$(
openssl x509 -in "$remote_cert" -noout -fingerprint -sha256 |
cut -d= -f2
)"
[[ "$local_fingerprint" == "$remote_fingerprint" ]] ||
alert "Nginx servește alt certificat decât versiunea activă"
end_date="$(openssl x509 -in "$remote_cert" -noout -enddate | cut -d= -f2-)"
logger -t acme-certificate-check --
"$DOMAIN este valid; expiră la $end_date"
printf 'OK: %s; expiră la %sn' "$DOMAIN" "$end_date"
Instalează scriptul:
sudo chmod 0755 /usr/local/sbin/check-acme-certificate
sudo /usr/local/sbin/check-acme-certificate
Output-ul reușit are forma:
OK: app.example.net; expiră la Dec 25 11:59:59 2026 GMT
Data este doar un exemplu; sistemul va afișa valoarea din certificatul real.
EXPIRY_SECONDS=2592000 reprezintă 30 de zile. openssl x509 -checkend returnează cod diferit de zero dacă certificatul va expira în intervalul indicat.
Punct de control: dacă amprentele diferă, verifică dacă există alt proxy TLS în fața Nginx sau dacă procesul nu a fost reîncărcat după schimbarea legăturii.
Pasul 7 — Programăm verificarea și păstrăm alerta în journal
De ce: timerul rulează controlul zilnic, inclusiv după o perioadă în care serverul a fost oprit. Eșecul serviciului rămâne vizibil ca stare failed, iar scriptul scrie alerta cu prioritatea daemon.alert.
Creează integral /etc/systemd/system/acme-certificate-check.service:
[Unit]
Description=Verifică certificatul ACME activ și servit de Nginx
After=network-online.target nginx.service
Wants=network-online.target
[Service]
Type=oneshot
DynamicUser=yes
ExecStart=/usr/local/sbin/check-acme-certificate
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
RestrictAddressFamilies=AF_INET AF_INET6
RestrictSUIDSGID=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
CapabilityBoundingSet=
SystemCallArchitectures=native
Creează integral /etc/systemd/system/acme-certificate-check.timer:
[Unit]
Description=Verifică zilnic expirarea certificatului ACME
[Timer]
OnCalendar=*-*-* 07:15:00
RandomizedDelaySec=30m
Persistent=true
Unit=acme-certificate-check.service
[Install]
WantedBy=timers.target
DynamicUser=yes creează o identitate temporară pentru fiecare execuție. Serviciul nu primește capabilities și nu poate modifica sistemul. Certificatul public este instalat cu modul 0644, astfel încât verificarea nu necesită acces la cheia privată.
Activează și testează:
sudo systemctl daemon-reload
sudo systemctl enable --now acme-certificate-check.timer
sudo systemctl start acme-certificate-check.service
systemctl status acme-certificate-check.service --no-pager
systemctl list-timers acme-certificate-check.timer
sudo journalctl
-u acme-certificate-check.service
--since today
--no-pager
Un serviciu oneshot reușit poate apărea ca inactive (dead) după terminare; câmpul relevant este Result: success, iar ultima execuție trebuie să conțină mesajul OK.
Output-ul orientativ al timerului este:
NEXT LEFT LAST PASSED UNIT
Sat 2026-09-27 07:32:... ... Sat 2026-09-26 07:... ... acme-certificate-check.timer
Momentul exact diferă din cauza RandomizedDelaySec=30m.
Pentru operațiunile recurente, acest control poate fi adăugat în checklistul lunar pentru servere Linux, alături de spațiu, loguri și backup.
Punct de control: dacă serviciul este failed, rulează journalctl -u acme-certificate-check.service -n 50; nu reseta starea înainte de a identifica certificatul sau conexiunea care a produs alerta.
Proba cap-coadă verifică DNS-ul, hook-ul și certificatul livrat
Rulează verificarea completă:
set -euo pipefail
. /etc/default/acme-atomic-deploy
sudo nginx -t
sudo certbot renew
--cert-name "$DOMAIN"
--dry-run
--run-deploy-hooks
sudo /usr/local/sbin/check-acme-certificate
test "$(systemctl is-enabled certbot.timer)" = "enabled"
test "$(systemctl is-enabled acme-certificate-check.timer)" = "enabled"
test "$(systemctl is-active nginx)" = "active"
local_fp="$(
sudo openssl x509
-in "/etc/nginx/tls-current/$DOMAIN/fullchain.pem"
-noout -fingerprint -sha256
)"
served_fp="$(
openssl s_client
-connect 127.0.0.1:443
-servername "$DOMAIN" </dev/null 2>/dev/null |
openssl x509 -noout -fingerprint -sha256
)"
test "$local_fp" = "$served_fp"
printf '%sn' 'VERIFICARE FINALĂ REUȘITĂ'
Rezultatul final cerut este:
nginx: configuration file /etc/nginx/nginx.conf test is successful
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/app.example.net/fullchain.pem (success)
OK: app.example.net; expiră la ...
VERIFICARE FINALĂ REUȘITĂ
Linia cu data expirării și formularea anumitor mesaje Certbot pot diferi între revizii. Condiția verificabilă este codul de ieșire zero, egalitatea amprentelor și mesajul final.
Mesajele care indică o validare, o cheie sau un reload nereușit
Detail: ... Invalid response from http://.../.well-known/acme-challenge/...: 404
Serverul ACME a ajuns la Nginx, dar tokenul nu a fost găsit în webroot. Verifică maparea înainte de o nouă cerere:
printf '%sn' test-acme |
sudo tee /var/www/acme/.well-known/acme-challenge/test-acme >/dev/null
curl --fail http://app.example.net/.well-known/acme-challenge/test-acme
sudo nginx -T | grep -A8 -F '.well-known/acme-challenge'
Detail: ... Timeout during connect (likely firewall problem)
Portul 80 nu este accesibil din internet sau DNS-ul indică altă adresă. Verifică ascultarea locală și înregistrările publice:
sudo ss -ltnp '( sport = :80 or sport = :443 )'
getent ahosts app.example.net
sudo journalctl -u nginx -n 50 --no-pager
Nu dezactiva permanent firewall-ul. Corectează regula pentru TCP 80 și 443 în firewall-ul serverului și în filtrarea furnizorului VPS.
Eroare: Cheia privată nu corespunde certificatului
Fișierele din lineage nu formează aceeași pereche sau au fost înlocuite manual. Certbot trebuie lăsat să administreze conținutul din /etc/letsencrypt. Inspectează certificatele cunoscute și încearcă reînnoirea oficială:
sudo certbot certificates
sudo certbot renew
--cert-name app.example.net
--dry-run
--run-deploy-hooks
Nu copia o altă cheie peste privkey.pem.
nginx: [emerg] cannot load certificate key ".../privkey.pem"
Nginx nu poate traversa directorul ori cheia nu are grupul și modul așteptate. Verifică fără să afișezi conținutul cheii:
sudo namei -l /etc/nginx/tls-current/app.example.net/privkey.pem
sudo find /etc/nginx/tls-releases/app.example.net
-maxdepth 2 -name privkey.pem -printf '%M %u:%g %pn'
sudo nginx -t
Cheia din release trebuie să fie root:www-data și 0640.
ALERTĂ: Nginx servește alt certificat decât versiunea activă
Procesul poate folosi o configurație diferită, un alt virtual host sau un proxy poate termina TLS înaintea lui. Compară sursa configurației și amprentele:
sudo nginx -T | grep -nE 'server_name|ssl_certificate'
sudo /usr/local/sbin/check-acme-certificate
sudo systemctl reload nginx
sudo /usr/local/sbin/check-acme-certificate
Dacă verificarea locală trece, dar testul public arată alt certificat, verifică load balancerul sau CDN-ul care termină conexiunea TLS.
Failed to start acme-certificate-check.service: Unit ... is masked
Unitatea a fost mascată și timerul nu o poate porni. Elimină masca, reîncarcă definițiile și testează:
sudo systemctl unmask acme-certificate-check.service
sudo systemctl daemon-reload
sudo systemctl start acme-certificate-check.service
systemctl status acme-certificate-check.service --no-pager
Restaurăm configurația HTTP și eliminăm automatizarea
Oprirea proiectului întrerupe HTTPS pentru domeniu. Execută această procedură numai într-o fereastră de mentenanță și numai dacă accepți revenirea la HTTP.
Oprește timerul propriu și păstrează certificatul Certbot până când confirmi că nu mai este utilizat:
sudo systemctl disable --now acme-certificate-check.timer
sudo rm -f /etc/systemd/system/acme-certificate-check.timer
sudo rm -f /etc/systemd/system/acme-certificate-check.service
sudo rm -f /usr/local/sbin/check-acme-certificate
sudo rm -f /etc/letsencrypt/renewal-hooks/deploy/50-nginx-atomic
if sudo test -f
/etc/nginx/sites-available/app.example.net.http-only.bak; then
sudo cp -a
/etc/nginx/sites-available/app.example.net.http-only.bak
/etc/nginx/sites-available/app.example.net
fi
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl daemon-reload
sudo systemctl reset-failed
După confirmarea configurației HTTP, elimină copiile versionate:
sudo rm -f /etc/nginx/tls-current/app.example.net
sudo rm -rf /etc/nginx/tls-releases/app.example.net
sudo rm -f /etc/default/acme-atomic-deploy
sudo rm -rf /var/www/acme
Doar dacă certificatul nu mai este folosit de niciun serviciu, șterge lineage-ul prin Certbot:
sudo certbot delete --cert-name app.example.net
Nu șterge manual directoarele din /etc/letsencrypt/live, archive sau renewal; comanda certbot delete menține evidența internă coerentă.
Lista de control pentru următoarea reînnoire
- ☐ Domeniul real indică adresa publică a serverului.
- ☐ Calea
/.well-known/acme-challenge/este accesibilă prin portul 80. - ☐
certbot.timerșiacme-certificate-check.timersunt activate. - ☐
certbot renew --dry-run --run-deploy-hooksse termină cu succes. - ☐ Hook-ul confirmă numele DNS și corespondența cheii publice.
- ☐
tls-currentindică un director complet dintls-releases. - ☐
nginx -ttrece înaintea fiecărui reload. - ☐ Certificatul servit are aceeași amprentă cu fișierul activ.
- ☐ Verificarea zilnică lasă un mesaj
OKîn journal. - ☐ Alertele
daemon.alertsunt colectate de sistemul central de monitorizare.
Următorul pas practic este trimiterea mesajelor daemon.alert către platforma de monitorizare a firmei, prin agentul deja aprobat în infrastructură. Pentru mai multe noduri Nginx, aceeași verificare trebuie executată separat pe fiecare nod sau integrată în health check-ul descris în ghidul despre Keepalived și HAProxy cu verificări aplicaționale. Într-un mediu cu DNS administrat prin API, validarea HTTP-01 poate fi înlocuită ulterior cu un plugin DNS oficial compatibil cu furnizorul, fără a schimba mecanismul atomic de activare.
Întrebări frecvente
De ce nu folosește Nginx direct /etc/letsencrypt/live?
Directorul separat permite verificarea și instalarea completă a perechii certificat–cheie înainte de schimbarea legăturii active. Nginx este reîncărcat numai după ce nginx -t trece.
Dry-run-ul Certbot poate porni deploy hook-ul?
Da. Comanda din tutorial folosește explicit --run-deploy-hooks împreună cu --dry-run, apoi verifică mesajul hook-ului în journal.
Cum este detectat un certificat nou care există pe disc, dar nu este servit?
Scriptul preia certificatul de la 127.0.0.1:443, cu numele domeniului transmis prin SNI, și compară amprenta SHA-256 cu certificatul din tls-current.
Ce se întâmplă dacă noua configurație Nginx nu trece testul?
Hook-ul restaurează legătura către versiunea anterioară și iese cu eroare. Versiunea care rulează rămâne disponibilă, iar cauza este înregistrată în journal.

















































