logrotate automatically archives, compresses and deletes old log files
(/var/log/*.log). While journald
manages the binary journal of systemd, logrotate takes care of the text
files written by services straight into /var/log/ (nginx, Apache, MySQL,
business applications). Without rotation, these files grow until they fill
the disk.
What you will learn
- Understand the layout of logrotate (
logrotate.conf+/etc/logrotate.d/) - Read and write a rotation policy for a service
- Use the compression, retention and permissions directives
- Run post-rotation scripts (reloading a service)
- Test a configuration safely and force a manual rotation
- Know the difference between logrotate and journald
In which context?
logrotate is essential as soon as a service writes text files, and the cases below are the ones that fill a disk in production:
- nginx or Apache produce
access.loganderror.logfiles that can reach several GB a day on a busy site - A business application writes its logs in
/var/log/myapp/without any built-in rotation - The
/vardisk fills up and triggers alerts, old logs must be compressed and purged - Compliance requires keeping logs 90 days, but not forever
logrotate and journald, two complementary roles
The two tools do not compete: each one handles a different kind of log, and knowing which one owns a file tells you where to look for its retention settings.
| Tool | Handles | Storage | Configuration |
|---|---|---|---|
| journald | Binary systemd journal | /var/log/journal/ | journald.conf |
| logrotate | Classic text files | /var/log/*.log | logrotate.conf + /etc/logrotate.d/ |
Both coexist on a modern system. journald captures the stdout/stderr streams of systemd services, syslog messages and kernel logs. logrotate manages the text files that services write directly to disk.
Installation
logrotate is preinstalled on most distributions. Otherwise:
sudo apt update && sudo apt install logrotatesudo dnf install logrotateThen check the installed version. It matters: directives such as maxsize or
hourly do not exist in old versions. An unknown directive does not block the
rest of the configuration, logrotate prints unknown option '...' -- ignoring line (prefixed with warning: in 3.22, error: in 3.21, measured) and goes
on, which is worse than a plain error since the policy then applies without
the option you thought you had set.
# Check the versionlogrotate --versionlogrotate 3.21.0This is the version of Ubuntu 24.04; Debian 13 and AlmaLinux 10 ship 3.22.0 (measured on 2026-09-30).
Configuration layout
The configuration of logrotate rests on two levels, and a per-service file only has to state what differs from the global defaults:
| File | Role |
|---|---|
/etc/logrotate.conf | Default global rules (frequency, retention) |
/etc/logrotate.d/ | One file per service that overrides the global rules |
Global configuration
This file describes no log in particular: it sets the default values that
every file of /etc/logrotate.d/ inherits. Look at the commented lines first,
they tell you what is not active. On Ubuntu 24.04, dateext and compress
ship disabled: believing otherwise makes you search for a long time why the
archives come out as an uncompressed .1. The file below is the one of Ubuntu
24.04; the two other reference distributions differ from it, see right after
the output.
cat /etc/logrotate.conf# see "man logrotate" for details
# global options do not affect preceding include directives
# rotate log files weeklyweekly
# use the adm group by default, since this is the owning group# of /var/log/.su root adm
# keep 4 weeks worth of backlogsrotate 4
# create new (empty) log files after rotating old onescreate
# use date as a suffix of the rotated file#dateext
# uncomment this if you want your log files compressed#compress
# packages drop log rotation information into this directoryinclude /etc/logrotate.d| Directive | Effect |
|---|---|
weekly | Weekly rotation by default |
rotate 4 | Keep 4 archived files |
create | Create a new file after rotation |
dateext | Suffix the archives with the date (-20260628) |
compress | Compress the archives (gzip) |
include /etc/logrotate.d | Load the per-service configurations |
The three distributions do not ship the same active values, measured on
2026-09-30: Debian 13 has no su root adm line, and AlmaLinux 10 turns
on dateext, so its archives carry the date (-20260930) instead of a
number. Read the file of your own machine rather than the one of a tutorial.
The include /etc/logrotate.d line must stay last: global options declared
after it do not apply to the files already included. The file shipped by the
distribution reminds you of it in a comment, on its third line.
Per-service configuration
Each service places its file in /etc/logrotate.d/, and this file belongs to
the package: a manual change will be flagged as a conflict at the next
update. Here is the file shipped by the nginx package on Debian 13, copied
as is.
cat /etc/logrotate.d/nginx/var/log/nginx/*.log { daily missingok rotate 14 compress delaycompress notifempty create 0640 www-data adm sharedscripts prerotate if [ -d /etc/logrotate.d/httpd-prerotate ]; then \ run-parts /etc/logrotate.d/httpd-prerotate; \ fi \ endscript postrotate invoke-rc.d nginx rotate >/dev/null 2>&1 endscript}Two details are worth a look. The prerotate block runs the scripts dropped by
other packages into httpd-prerotate, which lets a statistics tool, for
example, analyse the log before it is archived. And invoke-rc.d nginx rotate
triggers the rotate action of the init script, whose rotate_logs function
sends the USR1 signal to nginx (start-stop-daemon --stop --signal USR1): it
is not a full reload, just a reopening of the log files.
Essential directives
Frequency
These directives trigger nothing by themselves: they describe a condition
checked when logrotate runs, that is once a day through the systemd timer.
Writing daily therefore does not mean "at midnight sharp", but "at most once
a day, when it runs". The size line is apart: the manual says it is
mutually exclusive with the time criteria, and that the last directive
written wins.
| Directive | Rotation |
|---|---|
daily | Every day |
weekly | Every week |
monthly | Every month |
size 100M | When the file exceeds 100 MB |
Retention
The two directives below answer different questions and combine very well.
rotate limits the number of archives, which bounds the disk space;
maxage limits their age, which answers a compliance requirement ("do not
keep logs beyond N days"). One limit to know: the manual states that the age is
checked only when the log rotates. A file that notifempty skips because it
stayed empty no longer rotates, and so keeps its archives, maxage included:
for these dormant logs, only a scheduled purge (a find -mtime in a timer)
bounds the age.
| Directive | Effect |
|---|---|
rotate N | Keep N archives |
maxage N | Delete archives older than N days |
Compression
Compression is the most profitable lever on text logs: an access.log often
shrinks by a factor of 10 or more. The directive to understand is
delaycompress, which postpones compression by one cycle: the manual says it
serves when a program cannot be told to close its file and so keeps writing
for a few moments into the old one. It only has an effect combined with
compress.
| Directive | Effect |
|---|---|
compress | Compress the archives (gzip) |
delaycompress | Postpone compression by one cycle (useful if a service still writes) |
compresscmd /usr/bin/bzip2 | Use bzip2 instead of gzip |
nocompress | Disable compression |
Changing the command with compresscmd also changes the extension: measured
on logrotate 3.21 and 3.22, compresscmd /usr/bin/bzip2 produces a .1.bz2
archive, really compressed with bzip2, without any other directive. The manual
says so: the compressext extension "follows that of the configured compression
command" by default. compressext therefore only serves to impose another
extension, and uncompresscmd must follow the same command so that the reading
tools can open the archives.
Creating the new file
The create directive sets the permissions and the owner of the empty
file logrotate puts in place of the rotated one:
create 0640 root admIt creates the new file with permissions 0640, owner root, group adm. The
service can keep writing immediately.
Security
These three directives avoid the two most frequent failures in production: a
missing file that makes logrotate exit with an error, and a directory whose
owner is not root. su user group answers the second case and its absence is
the exact cause of the message error: skipping "..." because parent directory has insecure permissions, which logrotate completes itself with "Set "su"
directive in config file" (measured on the three distributions). Treat it as
mandatory as soon as the logs live outside /var/log.
| Directive | Effect |
|---|---|
missingok | Do not report an error if the file does not exist |
notifempty | Do not rotate an empty file |
su root adm | Run the rotation as this user/group (required if the directory has special permissions) |
Pre- and post-rotation scripts
The prerotate and postrotate blocks run shell commands before and after
the rotation, typically to make the service reopen its log files:
/var/log/myapp/*.log { daily rotate 7 compress missingok notifempty create 0640 myapp myapp sharedscripts postrotate systemctl reload myapp > /dev/null 2>&1 || true endscript}| Block | When |
|---|---|
prerotate ... endscript | Before the rotation |
postrotate ... endscript | After the rotation |
firstaction ... endscript | Before the first rotation (only once) |
lastaction ... endscript | After the last rotation (only once) |
sharedscripts guarantees that the script runs only once, even if several
files match the pattern, instead of reloading the service once per file.
Test and run
Test without applying (debug mode)
The -d flag is the first command to run on any new or changed
policy:
sudo logrotate -d /etc/logrotate.d/nginxThe -d flag simulates the rotation and shows what would be done,
without changing any file; logrotate --help describes it as "Don't do
anything, just test and print debug messages". Use it systematically before
putting a new configuration into production.
In the output, look for three things: the <file>:<n> unknown option line
(prefixed with warning: or error: depending on the version) that flags a
misspelled directive, the considering log lines that show the files retained,
and the conclusion per file (log needs rotating or log does not need rotating). A pattern that matches nothing stays unexpanded:
considering log /var/log/myapp/*.log, glob included, without a conclusion. On
a file logrotate has never seen, the conclusion is log does not need rotating (log has already been rotated): it records this first pass as its
starting point, which is normal.
Passed alone, a file of /etc/logrotate.d/ is read without
/etc/logrotate.conf: the global options do not apply. Measured on
AlmaLinux 10, whose global configuration turns on dateext,
logrotate -f /etc/logrotate.d/<file> produces a numbered .1 archive and not
a dated one. To test in real conditions, pass /etc/logrotate.conf.
Force the rotation
-f ignores the frequency and size conditions, and immediately rotates
everything that matches the configuration file passed as argument. It is the
validation command of a new policy, not an operations command: each run
consumes one notch of the rotate counter, so chaining it five times erases
your five oldest archives.
# Force the rotation of every filesudo logrotate -f /etc/logrotate.conf
# Force the rotation of a single servicesudo logrotate -f /etc/logrotate.d/nginxVerbose mode
The -v flag shows the details of each rotation performed, useful to
follow a forced rotation step by step:
sudo logrotate -v /etc/logrotate.confAutomatic triggering
On modern distributions, logrotate is triggered by a systemd timer (and no longer by cron). Here is the one of Ubuntu 24.04:
systemctl cat logrotate.timer[Unit]Description=Daily rotation of log filesDocumentation=man:logrotate(8) man:logrotate.conf(5)
[Timer]OnCalendar=dailyAccuracySec=1hPersistent=true
[Install]WantedBy=timers.targetTwo directives explain the behaviour observed on a server. AccuracySec=1h
gives systemd a one-hour margin to group this wake-up with others;
Debian 13 and AlmaLinux 10 carry RandomizedDelaySec=1h instead, a
random delay that spreads the rotations of a fleet of machines (measured on
2026-09-30). In both cases, the exact time varies from one day and one server to
the next, which is normal. Persistent=true catches up on the missed run if the
machine was off at the scheduled time, which keeps a server switched on
intermittently from piling up logs that never rotate.
# Check that the timer is activesystemctl status logrotate.timer
# See the next deadline and the last runsystemctl list-timers logrotate.timerWrite a complete policy
Here is an example of a policy for a business application, from the file creation to the validation of the first rotation:
-
Create the configuration file
Terminal window sudo nano /etc/logrotate.d/myapp -
Write the policy
/var/log/myapp/*.log {dailyrotate 30compressdelaycompressmissingoknotifemptycreate 0640 myapp myappsu myapp myappsharedscriptspostrotatesystemctl reload myapp > /dev/null 2>&1 || trueendscript} -
Test the configuration
Terminal window sudo logrotate -d /etc/logrotate.d/myappCheck: the output must show the files that would be rotated, without a syntax error.
-
Force a first rotation to validate
Terminal window sudo logrotate -f /etc/logrotate.d/myappls -la /var/log/myapp/Check: a
.1file must appear, uncompressed:delaycompresspostpones compression to the next cycle. Measured: a second-fproduces.1and.2.gz.
Troubleshooting
The symptoms below are told apart by a simple criterion: the rotation did not happen at all (first lines) or it happened but its result is surprising (next lines). Always start by checking the state of the timer, because a rotation that never runs produces exactly the same symptoms as an invalid configuration.
| Symptom | Probable cause | Solution |
|---|---|---|
| Logs never rotated | Timer disabled | systemctl enable --now logrotate.timer |
error: skipping "..." because parent directory has insecure permissions | Parent directory writable by a group other than root | Add su <user> <group> to the block |
unknown option '...' -- ignoring line | Typo, or directive missing from this version | Fix it, and compare with logrotate --version |
| Logs rotated but the service writes into the old one | No postrotate | Add a postrotate block with systemctl reload |
error: ... duplicate log entry | Same file in 2 configs | Check /etc/logrotate.d/ for duplicates |
| Uncompressed archives | compress missing | Add compress to the configuration |
A .1 file but no .1.gz | delaycompress active | Normal, compression applies at the next cycle |
# Quick diagnosissystemctl status logrotate.timer # Timer active?sudo logrotate -d /etc/logrotate.conf # Config errors?ls -la /var/log/nginx/ # Effective rotation?journalctl -u logrotate.service # Last run?Best practices
The first two lines will save you incidents, the next ones will save you
redoing the work. If you keep only one, keep the last: logrotate bounds the
retention, not the throughput. A service that writes 20 GB a day will
fill /var well before the daily run of the timer, and only a cap on the
application side or an aggressive size will change anything.
- Always test with
-dbefore changing a policy in production - Use
delaycompressfor services that may still write into the old file for a few seconds - Add
sharedscripts+postrotateto reload the service after rotation - Set restrictive permissions with
create, logs often contain sensitive information (IPs, errors, paths) - Configure
maxagealongsiderotate, to delete at each rotation the archives that are too old even if the counter is not reached - Separate the configurations in
/etc/logrotate.d/, one file per service, never everything inlogrotate.conf - Monitor disk space, logrotate does not solve the problem if logs grow faster than the rotation
Key points
- logrotate manages text log files, journald manages the binary journal of systemd. The two are complementary
- The global configuration lives in
/etc/logrotate.conf, the per-service configurations in/etc/logrotate.d/ - The key directives:
daily/weekly,rotate N,compress,delaycompress,create,postrotate logrotate -dsimulates the rotation without changing anything, to use before each change; passed alone, a file oflogrotate.d/ignores the global optionslogrotate -fforces the rotation, useful to validate a new configuration- The
postrotateblock reloads the service so that it writes into the new file - On modern systems, logrotate is triggered by a systemd timer (
logrotate.timer), no longer by cron
Knowledge check
Check that the essentials of this guide stuck. The questions cover only what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Linux networking fundamentals
A new full quiz with random questions
Work again only on the questions you missed