When managing production web servers, proper log rotation is crucial yet surprisingly tricky to implement natively in Nginx. Unlike PostgreSQL's elegant log_filename
with strftime formatting or Apache's rotatelogs
pipe solution, Nginx requires a more creative approach.
Nginx actually supports limited log rotation through its USR1
signal handling:
kill -USR1 cat /var/run/nginx.pid
Combined with cron and shell scripting, we can achieve rotation without logrotate:
#!/bin/bash
# /usr/local/bin/nginx_logrotate.sh
LOGS_PATH=/var/log/nginx
DATE=$(date +%Y-%m-%d)
mv $LOGS_PATH/access.log $LOGS_PATH/access-$DATE.log
mv $LOGS_PATH/error.log $LOGS_PATH/error-$DATE.log
kill -USR1 $(cat /var/run/nginx.pid)
For more sophisticated naming patterns (hourly/daily/monthly):
#!/bin/bash
# Hourly rotation with compression
LOG_DIR=/var/log/nginx
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
for LOG_TYPE in access error; do
mv $LOG_DIR/$LOG_TYPE.log $LOG_DIR/$LOG_TYPE-$TIMESTAMP.log
gzip $LOG_DIR/$LOG_TYPE-$TIMESTAMP.log
done
kill -USR1 $(pgrep -f "nginx: master")
This approach handles log rotation atomically:
#!/bin/bash
NGINX_PID=$(cat /var/run/nginx.pid)
DATE_EXT=$(date +%Y%m%d)
for LOG_FILE in /var/log/nginx/*.log; do
BASE=${LOG_FILE%.*}
mv "$LOG_FILE" "${BASE}-${DATE_EXT}.log"
done
# Graceful reload ensures continuous logging
kill -USR1 "$NGINX_PID"
Daily rotation at midnight:
0 0 * * * /usr/local/bin/nginx_logrotate.sh >/dev/null 2>&1
Add automatic cleanup for older logs:
# Delete logs older than 30 days
find /var/log/nginx -name "*.log" -mtime +30 -exec rm {} \;
While logrotate is the standard solution for log rotation on Linux systems, there are valid reasons to avoid it:
- Dependency reduction in containerized environments
- Precise control over rotation timing
- Avoiding the daily cron delay inherent in logrotate
Nginx actually supports time-based log rotation natively through its error_log
and access_log
directives when combined with Unix signals:
# In nginx.conf
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
Here's a complete solution using shell scripting and Nginx signals:
#!/bin/bash
LOGDIR="/var/log/nginx"
DATE=$(date +%Y-%m-%d)
# Rotate logs
mv ${LOGDIR}/access.log ${LOGDIR}/access-${DATE}.log
mv ${LOGDIR}/error.log ${LOGDIR}/error-${DATE}.log
# Reopen logs
kill -USR1 $(cat /var/run/nginx.pid)
# Compress old logs (optional)
find ${LOGDIR} -name "*.log" -mtime +7 -exec gzip {} \;
# Cleanup (optional)
find ${LOGDIR} -name "*.log.gz" -mtime +30 -delete
For daily rotation at midnight:
0 0 * * * /usr/local/bin/rotate_nginx_logs.sh
For real-time rotation similar to Apache's rotatelogs approach:
access_log | /usr/local/bin/custom_logger --format="%Y-%m-%d";
When implementing custom rotation:
- The USR1 signal approach causes minimal performance impact
- Pipe-based solutions may introduce bottlenecks under heavy load
- File compression should be deferred to off-peak hours