Incident Response

How to Purge MySQL Binary Logs Safely When They Fill the Disk

Binary logs are the most common reason a MySQL data directory quietly doubles in size. Deleting them is easy. Deleting them without breaking a replica or your point-in-time recovery takes five minutes of checking first.

· 15 min read · AG Data Team

The disk alert fires, you run du on the MySQL data directory, and most of the space is files called binlog.000xxx. The urge is to rm half of them. Don't. The right way to purge MySQL binary logs is one SQL statement, but only after you know which files your replicas, backups and recovery plan still need. This guide walks through that check, the purge itself and the settings that stop it from happening again.

Why binary logs pile up

In MySQL 8.4 binary logging is enabled by default and uses the row-based format. Without an explicit --log-bin, files are named binlog.NNNNNN and live in the data directory, next to an index file, binlog.index, that lists them in order. According to the binary log documentation, the server starts a new file when it starts or restarts, when logs are flushed, and when the current file reaches max_binlog_size (default and maximum 1GB). A single transaction is never split, so a huge batch job can produce a file far bigger than 1GB.

Old files are removed automatically once they are older than binlog_expire_logs_seconds. The default is 2592000 seconds, which is 30 days. That is the usual root cause: 30 days of row-based changes from a write-heavy application is simply more than the disk was sized for. Typical triggers:

Step 1: measure before you delete

df -h /var/lib/mysql
du -ch /var/lib/mysql/binlog.[0-9]* | tail -1
ls -lh /var/lib/mysql/binlog.index

Then look at the server's own view:

SHOW BINARY LOGS;        -- Log_name, File_size, Encrypted
SHOW BINARY LOG STATUS;  -- the file the server is writing right now

SELECT @@log_bin, @@log_bin_basename,
       @@binlog_expire_logs_seconds, @@binlog_expire_logs_auto_purge,
       @@max_binlog_size, @@binlog_transaction_compression;

SHOW BINARY LOGS needs the REPLICATION CLIENT privilege and lists every file with its size. If the numbers from du and from SHOW BINARY LOGS disagree a lot, find out why before you go further: files may have been copied in, deleted by hand or left behind outside the index.

Treat it as urgent. The manual's general disk-full section says MySQL checks every minute whether there is room to continue and writes a warning every 10 minutes. The binary log has its own rule, though: binlog_error_action defaults to ABORT_SERVER, which makes the server halt logging and shut down when it cannot write, flush or synchronize the binary log. Do not count on writes simply waiting until you free space.

Step 2: find out what is still needed

A binary log file is still in use if anything has not finished reading it. On every replica run:

-- on EVERY replica (and anything else that reads binlogs)
SHOW REPLICA STATUS\G
--   Source_Log_File:        binlog.004211   (receiver thread is reading this)
--   Relay_Source_Log_File:  binlog.004209   (applier has executed up to here)

The documentation defines Source_Log_File as the source file the receiver (I/O) thread is reading right now. That is the field that matters for purging, because everything before it has already been received by the replica. The oldest Source_Log_File across all replicas is your target: you may delete everything before it, and nothing from it onwards.

Two more consumers are easy to forget:

If a replica is very far behind, our guide to MySQL replication lag covers why that happens. It is worth fixing first, because that lag is what keeps old binlogs alive.

Step 3: purge with SQL, not rm

-- keep binlog.004211 and everything after it
PURGE BINARY LOGS TO 'binlog.004211';

-- or by time
PURGE BINARY LOGS BEFORE '2026-11-03 00:00:00';
PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY;

The PURGE BINARY LOGS statement deletes the files listed in the index before the given name or date, and updates binlog.index so the given file becomes the first one. It needs the BINLOG_ADMIN privilege. The official procedure is short: check SHOW REPLICA STATUS on every replica, take SHOW BINARY LOGS on the source, pick the earliest file any replica is reading, back up the files you are about to delete (optional but advisable), then purge up to but not including that file.

It is safe to run while replicas are replicating; you do not need to stop them. If a connected replica is reading one of the files you asked to delete, MySQL keeps that file and everything after it, deletes only the older ones and issues a warning. That protection covers connected replicas only. It does nothing for one that is offline.

Two limits: PURGE BINARY LOGS cannot run while LOCK INSTANCE FOR BACKUP is in effect, and it has no effect if binary logging is disabled.

Why rm makes things worse

Deleting files by hand leaves binlog.index pointing at files that no longer exist. The manual is explicit about the result: both PURGE BINARY LOGS TO and PURGE BINARY LOGS BEFORE then fail with an error. The documented fix is to edit the index file (it is plain text), leave only the files that really exist, and run the purge again. So rm breaks the very tool you need next time, and it gives you no protection at all for replicas that are still reading.

If you have already done it, fix the index before the next rotation or restart, and check every replica. A replica that needs a purged file cannot continue. The server error reference has the messages to look for in Last_IO_Error: error 1236, "Got fatal error ... from source when reading data from binary log", and for GTID setups ER_SOURCE_HAS_PURGED_REQUIRED_GTIDS, "Cannot replicate because the source purged required binary logs." That message itself lists the options: get the missing transactions from elsewhere, or rebuild the replica from a backup. Our article on replication error 1062 explains why skipping events to get a replica going again is a bad idea; the same applies here.

Keep a copy before you purge

If you are not sure whether you will need the files, move them off the server instead of losing them. The mysqlbinlog backup mode copies binary logs in their original format over a normal client connection:

mysqlbinlog --read-from-remote-server --raw \
  --host=db1 --user=backup --password \
  --result-file=/backup/binlog/ \
  binlog.004100 binlog.004101 binlog.004102

--read-from-remote-server and --raw are the two required options. Note the documented catch: copies of encrypted binary logs made this way are stored unencrypted, so protect the backup location accordingly.

Step 4: stop it from happening again

Set retention on purpose instead of living with the 30-day default:

[mysqld]
# 7 days; must be longer than the worst replica lag
# and cover the gap between your full backups
binlog_expire_logs_seconds = 604800
binlog_transaction_compression = ON
SET PERSIST binlog_expire_logs_seconds = 604800;
FLUSH BINARY LOGS;   -- rotation gives the server a chance to expire old files

Managed MySQL: Amazon RDS

On RDS for MySQL, binary log retention is set with a stored procedure. The AWS documentation says the default binlog retention hours is NULL, which means binary logs are not retained, and the maximum is 168 hours (7 days):

CALL mysql.rds_set_configuration('binlog retention hours', 24);

If storage on an RDS instance keeps growing, check whether someone set a long retention for an external replica or a CDC tool that no longer exists.

Key takeaways

MySQL Binary log Replication Disk space
Work with us

Disk filling up on a MySQL server?

We free space safely, keep replicas and recovery intact, and set retention so the same alert does not come back next month.

Get in touch More from the blog