NGINX logging and log rotation

Logging

There are two important NGINX directives to enable logging; access_log and error_log. The simplest configuration can just specify log file paths and log level for the error_log:

http {
    ...
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log error;
    ...
}

The access_log logs every request, whereas the error_log directive is used for logging only error messages. If present, NGINX will happily log to these locations, and we can use general file tools to view or search the logs for any issues.

The error log levels are warn, error, crit, alert, and emerg. You probably want error for all errors or warn to catch warnings as well.

You can edit the log format with log_format directive. In the following example, we'll use a log format compression (you can choose your own name) that adds $gzip_ratio of the request:

http {
  log_format compression
    '$remote_addr - $remote_user [$time_local] '
    '"$request" $status $body_bytes_sent '
    '"$http_referer" "$http_user_agent" "$gzip_ratio"';

  server {
    gzip on;
    access_log /var/log/nginx/access.log compression buffer 32k;
    ...
  }
}

I also added a buffer of a certain size that indicates the logs' logs are written to the file. In general, stick to defaults unless you have a good reason not to.

The other variables like $remote_addr and $http_referer are quite self-descriptive, but as always, it's good to consult the NGINX documentation for more details and options.

Some might prefer to log in the JSON format for easier consumption from 3rd-party tools. Here is an example how this could look like:

log_format main '{'
                 '"remote_addr": "$remote_addr",'
                 '"remote_user": "$remote_user",'
                 '"time_local":  "$time_local",'
                 '"request":     "$request",'
                 '"status":      "$status",'
                 '"body_bytes_sent": "$body_bytes_sent",'
                 '"http_referer": "$http_referer",'
                 '"http_user_agent": "$http_user_agent"'
                 '}';

Similar to the system journal, NGINX logs will grow in size quite quickly. We already saw the [flush=time] option for the access_log directive that deletes the log file contents after a specific time. Unfortunately, this option will clear the logs completely and, therefore, not give us a chance to keep a few days of log data available for inspection. Also, it does not apply to error_log. We won't use the flush option to solve this need but introduce an external log rotation.

Log Rotation With logrotate

logrotate is a daemon designed to help with running programs that generate large log files. It allows rotation, compression, removal, and even mailing of discarded log files. It can be instrumented to rotate logs in a timely fashion or when it grows too large.

logrotate should be already installed on our system (as the logrotate RPM package) with a default configuration at /etc/logrotate.conf configuration file:

# see "man logrotate" for details
# rotate log files weekly
weekly

# keep 4 weeks worth of backlogs
rotate 4
...

# packages drop log rotation information into this directory
include /etc/logrotate.d

The file is self-explanatory. As with other programs, we can drop our configurations in the /etc/logrotate.d directory, and logrotate will use them. The syntax for the file is as follows:

/var/log/name/of/log/file.log {
    rotate 5
    monthly
}

/var/log/name/of/frequent/log/file.log {
    daily
}

This way, we can specify one or more log files and override (or add to) the default log rotation settings.

Most common directives we can specify in our logrotate configuration file:

When we install NGINX from the Fedora or CentOS repository it already ships with its own logrotate settings:

$ sudo cat /etc/logrotate.d/nginx
/var/log/nginx/*log {
    create 0664 nginx root
    daily
    rotate 10
    missingok
    notifempty
    compress
    sharedscripts
    postrotate
        /bin/kill -USR1 `cat /run/nginx.pid 2>/dev/null`
          2>/dev/null || true
    endscript
}

The postrotate directive specifies a script that runs after the rotation is done. In this case, it sends the nginx process the USR1 signal which is the official way of telling NGINX to re-open the logs again.

Having this file in place means our job is already done for us when it comes to NGINX log rotation.

A Cron job for logrotate is provided for us (/etc/cron.daily/logrotate) so we only have to ensure that a crond service is running. On newer Fedora systems, logrotate has its systemd service called logrotate that replaces the Cron job approach.