What the Certbot timer actually does
certbot.timer, that starts certbot.service on a schedule, typically twice a day. Snap installs use snap.certbot.renew.timer instead, and some older setups rely on a cron entry. The exact names depend on how Certbot was installed.
Each run executes Certbot's renew command. It reads every certificate config under /etc/letsencrypt/renewal/ and renews only the certificates close to expiry, by default within 30 days. Most runs do nothing at all.
The timer is a scheduler, not a supervisor. It starts the job and records the exit status. It does not retry with a different strategy, it does not page anyone, and it does not care that the same failure has repeated for three weeks. Automation that never reports failure is only half automated.
Why renewal needs free blocks and free inodes
/etc/letsencrypt/archive/<domain>/. The symlinks in /etc/letsencrypt/live/<domain>/ get updated. The renewal config may be rewritten. A fresh entry goes into /var/log/letsencrypt/letsencrypt.log. With the webroot plugin, a challenge file is created under .well-known/acme-challenge/ in the site's document root. Python may also create temporary files along the way.
Each of those files needs two things: data blocks for its content, and an inode. An inode is the filesystem's metadata record for a file: owner, permissions, timestamps, and where the data lives. On ext4 the number of inodes is fixed when the filesystem is created. Once they are used up, no new file can be created, even with gigabytes of free space. XFS and Btrfs allocate inodes more flexibly, so this is mostly an ext4 story, but ext4 is the default on many VPS images.
Both conditions produce the same kernel error, ENOSPC, which shows up as No space left on device. This is where people get misled. Someone runs df -h, sees the disk at 60 percent, and decides the error must be wrong. It isn't. The disk ran out of a different resource.
The two usual causes look different. Runaway logs fill blocks: an application left in debug mode, a plugin writing the same warning on every request, or an access log that rotation never touches. Inode exhaustion comes from huge numbers of small files: PHP session files that are never garbage collected, cache plugins writing a file per page variant, a stuck mail queue, or years of rotated logs that were never deleted. Certbot also keeps its own rotated logs, by default up to a large number of backups. It is rarely the main cause, but it is one more directory of small files.
Why the failure stays quiet
Finding the error in Certbot's logs
BASH
# When did the timer last fire, and when will it fire next?
systemctl list-timers | grep -i certbot
# Did the last run succeed? (snap installs: snap.certbot.renew.service)
systemctl status certbot.service
journalctl -u certbot.service --since '14 days ago'
# Search Certbot's own log and its rotated copies for the disk error
sudo grep -i 'no space left' /var/log/letsencrypt/letsencrypt.log*
# Show every certificate with its expiry date
sudo certbot certificates
# Test renewal against the staging environment once space is freed
sudo certbot renew --dry-runTEXT
# Illustrative output format, not from a real system
OSError: [Errno 28] No space left on devicecertbot certificates shows an expiry date that is weeks old relative to its last successful renewal, renewals have been failing for a while regardless of what the logs say.
Checking inodes and log rotation
IUse%. Anything close to 100 percent on the filesystem holding /etc, /var, or the document root will break renewal.
BASH
# Block usage and inode usage for the root filesystem
df -h /
df -i /
# Which directories hold the most files? Stays on one filesystem. Can be slow.
sudo du --inodes -x -d 3 / 2>/dev/null | sort -n | tail -20
# Which directories hold the most data under /var?
sudo du -xh -d 2 /var 2>/dev/null | sort -h | tail -20
# Dry-run logrotate: shows what would be rotated without changing anything
sudo logrotate -d /etc/logrotate.conf/etc/logrotate.d/, or it has a rule pointing at a path that changed after an upgrade or migration.
Be careful with cleanup. Deleting a log that a running process still holds open does not free its space until that process closes the file or restarts. Truncating the file is often safer than removing it. For inode problems, deleting stale session or cache files fixes the immediate issue, but the files come back unless you fix what creates them. Never delete anything under /etc/letsencrypt/ to free space. The archive directory is small, and the live symlinks point into it.
Once there is room again, run the dry run, then a real renewal, and confirm the web server picked up the new certificate. Certbot's --deploy-hook option is the usual way to reload Nginx or Apache automatically after a successful renewal.
Making it part of routine maintenance
df -h, df -i, the last few runs in the Certbot service journal, and the expiry dates from certbot certificates. That takes under a minute and catches the problem with weeks of margin left.
If you have monitoring, alert on inode usage the same way you alert on disk usage. A threshold such as 85 percent works as an illustrative example. Alert on certificate expiry measured from outside the server, by checking the certificate's actual expiry date rather than only whether the page loads. An external check catches every cause of failed renewal, including the ones that have nothing to do with disk.
Certbot's timer does its job: it keeps trying. It just has no way to tell you that every try is failing. Treat free blocks, free inodes, and working log rotation as prerequisites for renewal, and check them on a schedule you control.