Implementing Automated Nginx Log Rotation with Date-Based Naming Without logrotate


2 views

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