
Most “disk full” incidents on a VPS aren’t caused by your database. They come from logs that never rotate: a noisy PHP app, a busy Nginx access log, or a debug flag left on all weekend. This Linux VPS log rotation setup tutorial shows how to harden logrotate on Ubuntu 22.04/24.04 and AlmaLinux/Rocky 9/10. The goal is bounded logs, reliable compression, and clean log file reopen behavior.
You’ll define a rotation policy for common hosting stacks (Nginx, Apache, PHP-FPM, MySQL/MariaDB). You’ll also add guardrails to prevent permission breakage. Finally, you’ll verify everything with dry runs and forced rotations.
The outcome should be boring: predictable disk usage and fewer 3am surprises.
What you’ll set up (and what you’ll avoid)
- Rotate & compress logs on schedule (daily/weekly) with sensible retention.
- Keep permissions intact using
suandcreate. - Reload Nginx/Apache/PHP-FPM cleanly after rotation so they keep writing to the new file.
- Prevent “logrotate ran, but logs still grew” failures by validating postrotate hooks.
- Avoid monitoring-agent talk and vendor tooling; this stays focused on rotation and disk safety.
Prerequisites: a VPS where you can reproduce the problem safely
You need root (or sudo) and a server that writes logs locally. If you’re provisioning a new host, pick a VPS sized for your traffic and logging volume.
For predictable I/O and enough headroom for retention, a HostMyCode VPS with NVMe storage makes rotation and compression noticeably faster under load.
Useful baseline checks:
df -hT(disk usage and filesystem type)sudo du -xh /var/log | sort -h | tail -n 30(largest log trees)sudo journalctl --disk-usage(systemd journal footprint)
If you’re already firefighting low disk, reduce pressure first. Truncate the worst offender only after you confirm you don’t need that file for forensics.
sudo du -sh /var/log/* | sort -h
sudo truncate -s 0 /var/log/nginx/access.log
Then come back and fix the root cause with rotation.
Step 1 — Confirm logrotate is installed and how it runs on your OS
On Ubuntu/Debian, logrotate usually runs via a systemd timer. Older builds may still use cron.
On AlmaLinux/Rocky, it’s typically triggered by /etc/cron.daily. Some images also ship a systemd timer.
Ubuntu 22.04/24.04
sudo apt-get update
sudo apt-get install -y logrotate
systemctl list-timers --all | grep -i logrotate || true
systemctl status logrotate.service logrotate.timer
AlmaLinux/Rocky 9/10
sudo dnf install -y logrotate
rpm -q logrotate
ls -l /etc/cron.daily/logrotate || true
systemctl list-timers --all | grep -i logrotate || true
Either trigger method is fine. What matters is consistency.
Logrotate must run regularly, and your rules must match how your services write logs.
Step 2 — Review your global defaults (and fix the two settings that bite most admins)
Start with the main config:
- Debian/Ubuntu:
/etc/logrotate.confand includes from/etc/logrotate.d/ - AlmaLinux/Rocky: same layout, with slightly different distro defaults
sudo sed -n '1,200p' /etc/logrotate.conf
Two defaults cause most “it ran but didn’t work” situations:
- Missing
su: many rotations needsu root adm(or similar) to handle group-owned logs safely. - Wrong
createowner/mode: recreating the file with the wrong user/group breaks writes and can leave you blind.
On Ubuntu, the adm group is common for log access. Check what your key logs actually look like:
ls -l /var/log/nginx/access.log /var/log/syslog 2>/dev/null || true
Resist the urge to “fix everything” in global defaults. Use per-service files under /etc/logrotate.d/.
That keeps owners and modes explicit.
Step 3 — Implement a safe rotation policy for Nginx (with verification)
On busy sites, Nginx access logs grow fast. A good baseline is daily rotation, two weeks of retention, and delayed compression.
Delayed compression keeps yesterday’s log easy to grep.
You also need a reload after rotation. Without it, Nginx can keep writing to an old file handle.
Create or edit /etc/logrotate.d/nginx:
sudo editor /etc/logrotate.d/nginx
Use a policy like this (works well on Ubuntu/Alma/Rocky with systemd):
/var/log/nginx/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
dateext
dateformat -%Y%m%d
sharedscripts
su root adm
create 0640 www-data adm
postrotate
/usr/sbin/nginx -t && systemctl reload nginx >/dev/null 2>&1 || true
endscript
}
Adjust the user to match your distro and service account:
- Ubuntu often runs Nginx as
www-data. - AlmaLinux/Rocky commonly use
nginx. Confirm:ps -o user,group,cmd -C nginx
If your Nginx logs are owned by nginx:nginx, update:
create 0640 nginx nginx
su root nginx
Verify Nginx rotation without waiting a day
sudo logrotate -d /etc/logrotate.d/nginx
sudo logrotate -vf /etc/logrotate.d/nginx
ls -lh /var/log/nginx | head
sudo systemctl status nginx --no-pager
A classic failure mode is subtle. Rotation happens, but Nginx keeps writing to the old file because the reload didn’t run.
In that case, the “rotated” file keeps growing.
Confirm which file is still open:
sudo lsof | grep '/var/log/nginx/access.log' || true
Step 4 — Rotate PHP-FPM logs (and avoid permission regressions)
PHP-FPM logging varies by distro and packaging. You might see:
/var/log/php8.3-fpm.log(Debian/Ubuntu packaging varies)- pool-specific logs configured in
/etc/php/8.3/fpm/pool.d/www.conf
First, confirm what’s actually in use:
sudo systemctl status php8.3-fpm --no-pager | sed -n '1,120p'
sudo grep -R "^error_log" -n /etc/php/8.*/fpm/pool.d 2>/dev/null || true
Create /etc/logrotate.d/php-fpm:
/var/log/php*-fpm.log {
weekly
rotate 8
missingok
notifempty
compress
delaycompress
su root adm
create 0640 root adm
postrotate
systemctl reload php8.3-fpm >/dev/null 2>&1 || true
systemctl reload php8.2-fpm >/dev/null 2>&1 || true
endscript
}
If you only run one PHP-FPM version, keep the single matching reload line.
The || true guards keep rotation from failing when a service isn’t installed.
Step 5 — Apache logs: copytruncate vs reload (choose intentionally)
With Apache, you can rotate by reloading the service (preferred). Or you can use copytruncate (simpler, but it can drop lines during heavy writes).
If you can reload safely, do that.
Edit /etc/logrotate.d/apache2 (Ubuntu) or /etc/logrotate.d/httpd (Alma/Rocky). A sensible pattern:
/var/log/apache2/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
dateext
sharedscripts
su root adm
postrotate
/usr/sbin/apachectl configtest && systemctl reload apache2 >/dev/null 2>&1 || true
endscript
}
On AlmaLinux/Rocky, replace with:
systemctl reload httpd
If you’re running a control panel, tread carefully. cPanel/WHM and Plesk may rewrite web server configs.
Logrotate tweaks are usually fine, but write down what you changed. That makes re-applying changes after updates painless.
If you’re still choosing a panel for a new server, best VPS control panels for Linux hosting in 2026 is a helpful overview of the operational trade-offs.
Step 6 — MySQL/MariaDB logs: rotate slowly, keep audit value
Database logs aren’t always large. Slow query logs can still explode during an incident.
Depending on your setup, MySQL/MariaDB may write to:
/var/log/mysql/error.log(Debian/Ubuntu)/var/log/mysqld.logor journald only (Alma/Rocky varies)
Confirm the real paths on your host:
sudo mysql -e "SHOW VARIABLES LIKE 'log_error';" 2>/dev/null || true
sudo mysql -e "SHOW VARIABLES LIKE 'slow_query_log_file';" 2>/dev/null || true
sudo systemctl status mysql mariadb --no-pager | sed -n '1,120p'
If you have file-based logs, create /etc/logrotate.d/mysql-custom (the filename is up to you):
/var/log/mysql/*.log {
weekly
rotate 12
missingok
notifempty
compress
delaycompress
su root adm
create 0640 mysql adm
sharedscripts
postrotate
# Ask MySQL to reopen log files
/usr/bin/mysqladmin --defaults-file=/etc/mysql/debian.cnf flush-logs 2>/dev/null || true
endscript
}
On AlmaLinux/Rocky, you may not have /etc/mysql/debian.cnf. Use a dedicated maintenance user, or skip flush-logs if you’re journald-only.
Don’t put a root password in a logrotate file.
If you also need a database backup plan (separate from rotation), see production database backup strategies in 2026. Rotation reduces disk risk; it doesn’t protect your data.
Step 7 — systemd journal: cap it so it can’t eat your root filesystem
On systemd-based distros, journald grows until it hits its internal limit. That limit is often “a percentage of /var.”
On smaller VPS disks, that can still be far too generous.
Check current size:
sudo journalctl --disk-usage
Set explicit limits in /etc/systemd/journald.conf (create overrides if you prefer). This baseline works well for small-to-mid VPS hosts:
sudo editor /etc/systemd/journald.conf
[Journal]
SystemMaxUse=1G
SystemKeepFree=2G
SystemMaxFileSize=200M
MaxRetentionSec=14day
Reload journald:
sudo systemctl restart systemd-journald
sudo journalctl --disk-usage
Tune these numbers to your disk size and how much history you actually use.
If you’re unsure what’s realistic, Linux VPS capacity planning in 2026 helps you avoid long retentions on tiny root volumes.
Step 8 — Add a “disk full early warning” check (simple, effective)
Rotation is control; alerts are detection. Even without a monitoring stack, a daily cron or systemd timer can warn you when disk crosses a threshold.
Create /usr/local/sbin/disk-usage-check.sh:
sudo tee /usr/local/sbin/disk-usage-check.sh >/dev/null <<'EOF'
#!/bin/sh
set -eu
THRESHOLD="85"
MOUNT="/"
USE=$(df -P "$MOUNT" | awk 'NR==2{gsub(/%/,"",$5); print $5}')
if [ "$USE" -ge "$THRESHOLD" ]; then
logger -p user.warning "Disk usage on $MOUNT is ${USE}% (threshold ${THRESHOLD}%)"
fi
EOF
sudo chmod 0755 /usr/local/sbin/disk-usage-check.sh
Run it now:
sudo /usr/local/sbin/disk-usage-check.sh
sudo tail -n 30 /var/log/syslog 2>/dev/null || sudo journalctl -n 30
Schedule it with cron:
sudo crontab -e
15 2 * * * /usr/local/sbin/disk-usage-check.sh
If you later build a full monitoring stack, keep it focused and low-noise: disk, load, memory, and HTTP error rates.
Linux VPS performance monitoring in 2026 has a practical checklist that maps well to day-to-day hosting ops.
Step 9 — Validate your whole logrotate configuration (and read the state file)
After you edit a few policies, test the full run. On any distro:
sudo logrotate -d /etc/logrotate.conf
Then force a run. This is useful during a maintenance window:
sudo logrotate -vf /etc/logrotate.conf
Logrotate records what it rotated in /var/lib/logrotate/status. If you’re convinced “rotation isn’t happening,” this file usually explains why.
Common causes include “already rotated today,” a path mismatch, or missing files.
sudo tail -n 80 /var/lib/logrotate/status
Step 10 — Common breakages and how to fix them fast
- “Permission denied” during rotation
Addsuinside the specific block (preferred), and make sure directory permissions allow rotation. Check ownership:namei -l /var/log/nginx. - Logs stop updating after rotation
The daemon didn’t reopen the file. Fix thepostrotatereload, and verify the command exists (nginx -t,apachectl configtest). - Rotated files aren’t compressed
If you useddelaycompress, the newest rotated file stays uncompressed until the next run. That’s expected behavior. - Rotation creates logs with wrong owner/mode
Correct thecreateline to match the daemon user/group. Then generate traffic and check withls -l. - Disk still fills even with rotation
Usually it’s (a) app logs outside/var/log, (b) large temp files in/tmp, or (c) deleted-but-open files. Find open deleted files:sudo lsof +L1.
Optional: ship logs off the VPS (so you can keep retention short locally)
If you need 30–90 days of logs for compliance or incident response, don’t keep all of that on the VPS. Rotate locally for disk safety, then forward logs elsewhere for retention.
If that’s your next step, follow Linux VPS log shipping with rsyslog and keep local retention lean (7–14 days).
Summary: a practical log rotation baseline for hosting servers
A good Linux VPS log rotation setup should feel unremarkable. Rotate web logs daily. Rotate app and database logs weekly. Cap journald.
Then confirm daemons reopen files after rotation.
Do that, and disk-full outages become an exception instead of a pattern.
If you want this handled alongside patching, web stack updates, and backup checks, managed VPS hosting from HostMyCode is the clean option. If you prefer full control, start with a fast NVMe plan on a HostMyCode VPS and apply the policies above on day one.
If you run client sites or production WordPress on your own server, log growth is an easy way to get surprised by downtime. HostMyCode can provision a VPS with predictable storage performance and help you stay on top of the basics—rotation, backups, and updates.
Start with a HostMyCode VPS, or hand off the ongoing work to managed VPS hosting if you’d rather focus on your sites than the server.
FAQ
Should I use copytruncate to rotate logs without reloading services?
Use it only when you can’t reload the service. Reloading is safer and keeps log integrity intact. copytruncate can drop lines during high write rates.
How many days of logs should I keep on a VPS?
For most hosting servers, 7–14 days locally is enough if you also ship logs or have backups. High-traffic sites often need shorter retention unless you allocate extra disk.
Why did my log file become owned by root after rotation?
Your logrotate block likely has a create line with the wrong owner/group. Set it to the daemon user (for example, www-data or nginx) and verify with ls -l after a forced rotation.
How do I confirm Nginx is writing to the new log file after rotation?
Force a rotation, reload Nginx, then watch the new file size increase. For a definitive check, use lsof to confirm which path the Nginx worker processes have open.
Does journald replace logrotate?
No. journald has its own retention controls, while many services still write plain files in /var/log. Treat them as separate storage pools and cap both.