A working baseline
Certbot's nginx plugin (certbot --nginx) issues the certificate, writes it to /etc/letsencrypt/live/<hostname>/, and edits the nginx config to point at it. Certbot installs a systemd timer (or cron job, on older installs) that runs certbot renew twice a day, which only actually renews certificates within 30 days of expiry — so it's safe to run frequently.
Where it actually breaks
The HTTP-01 challenge gets blocked. Certbot's default validation method needs port 80 reachable from the public internet for the challenge request. A firewall change, a reverse proxy in front of nginx, or a redirect that sends /.well-known/acme-challenge/ to HTTPS before Certbot can respond will all break renewal — quietly, since the current certificate keeps working right up until it expires.
nginx never reloads. Certbot renewing the certificate files doesn't make nginx pick them up — nginx only reads the certificate at startup or reload. Without a --deploy-hook (or the equivalent in /etc/letsencrypt/renewal-hooks/deploy/) running nginx -s reload, the renewed certificate sits on disk while nginx keeps serving the old, soon-to-expire one.
The renewal timer itself stops running. A server rebuild, an OS upgrade that resets systemd units, or a container restart that doesn't persist the timer can all leave nothing actually calling certbot renew anymore, with no error until the certificate is already gone.
Confirm it's actually working
The only way to know renewal is genuinely working — not just configured — is to check what the server is actually serving, independent of what the renewal tooling believes it did. That's a plain external scan, not a look at Certbot's own logs.