Report this

What is the reason for this report?

How To Configure Logging and Log Rotation in Nginx on Ubuntu

Updated on February 3, 2026
How To Configure Logging and Log Rotation in Nginx on Ubuntu

Introduction

Nginx logs provide the data you need to troubleshoot failures, analyze traffic, and verify application behavior. Without a proper logging setup, diagnosing production issues becomes guesswork.

This tutorial shows you how to configure access and error logs, set up log rotation to prevent disk exhaustion, and verify your configuration with real commands.

By default, Nginx stores logs in /var/log/nginx/ and uses the logrotate utility to manage log files automatically. You’ll use an Ubuntu virtual private server as an example, but the configuration applies to any modern Linux distribution running Nginx.

This tutorial was last validated on Ubuntu 22.04 LTS with the Ubuntu-packaged Nginx stable release.

Key Takeaways

  • Nginx uses two primary log types: access logs for request visibility and error logs for operational diagnostics
  • Choose error log levels deliberately: warn is a practical default for production observability, while error minimizes noise; reserve debug only for short-term troubleshooting
  • Log rotation is mandatory on production servers: Ubuntu’s logrotate prevents uncontrolled disk growth and enables long-term retention
  • Custom log formats improve observability: JSON logs simplify ingestion into centralized logging platforms such as Elasticsearch and Datadog
  • Buffered logging reduces I/O overhead: enabling buffer=32k is recommended for high-traffic environments
  • Always validate before reloads: use nginx -t to prevent configuration errors from causing downtime
  • Conditional logging reduces noise: exclude health checks and static assets to keep logs focused on actionable traffic
  • Separate logs per site for clarity: per-domain log files simplify debugging and incident response
  • Monitor disk usage proactively: alert on log partitions before they exceed safe capacity thresholds
  • Centralized logging is essential at scale: multi-server deployments should aggregate logs using syslog or log shipping agents

Quick reference

Use this cheat sheet to validate changes quickly:

  • Logs directory: /var/log/nginx/
  • Access log: /var/log/nginx/access.log
  • Error log: /var/log/nginx/error.log
  • PID file: /run/nginx.pid
  • Test config: sudo nginx -t
  • Reload Nginx: sudo systemctl reload nginx
  • Dry-run logrotate: sudo logrotate -d /etc/logrotate.d/nginx
  • Force logrotate once: sudo logrotate -f /etc/logrotate.d/nginx

Prerequisites

To follow this tutorial, you will need:

With Nginx running on your Ubuntu server, you’re ready to begin.

Quick baseline: production-friendly logging

If you want a safe default you can deploy immediately, start here and refine later:

# /etc/nginx/nginx.conf
# Place these inside the http { } block
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log combined buffer=32k;

map $request_uri $log_healthcheck {
    default 1;
    ~^/healthz$ 0;
}

Then apply per-site logging in your server {} block (commonly in /etc/nginx/sites-available/your_site):

# /etc/nginx/sites-available/your_site
server {
    access_log /var/log/nginx/access.log combined if=$log_healthcheck;

    location = /healthz {
        access_log off;
        return 200;
    }
}

This configuration balances signal quality, disk I/O, and operational safety for most production Ubuntu deployments.

Understanding Nginx Log Types

Nginx uses two main logs: access (for every request) and error (for diagnostics). Each serves a distinct purpose in monitoring and troubleshooting.

Where logging directives belong

Use these placement rules to avoid unexpected overrides:

  • Use the http {} block for global defaults (log formats, default paths).
  • Use server {} blocks to separate logs per site or domain.
  • Use location {} blocks only for targeted overrides such as disabling logs for health checks or static assets.

Nginx vs Apache: logging at a glance

If you’re used to Apache: Nginx error_log is analogous to Apache’s ErrorLog; Nginx access_log plus log_format is analogous to CustomLog and LogFormat. Both support conditional logging and rotation via external tools (e.g. logrotate). A practical difference is that Nginx does not rotate logs itself—you must use logrotate or a script and then signal Nginx (e.g. kill -USR1) to reopen files. Apache’s rotatelogs pipes logs to a rotating process; on Nginx you rely on the OS or logrotate to move/compress files and then signal the process.

Access Logs

Access logs record every request processed by Nginx, including the client IP, request method, URI, response status code, bytes sent, user agent, and referrer. These logs are invaluable for:

  • Traffic trends: Identifying top endpoints, referrers, and status-code patterns
  • Performance debugging: Spotting slow requests and bandwidth-heavy responses
  • Security visibility: Detecting suspicious request patterns and abuse

By default, Nginx writes access logs to /var/log/nginx/access.log using the combined log format, which captures comprehensive request data in a standardized format compatible with most log analysis tools.

Error Logs

Error logs capture diagnostic information when something goes wrong, from minor warnings to critical system failures. They include:

  • Server errors: Configuration problems, upstream failures, or resource exhaustion
  • Client errors: Invalid requests, missing files (404s), or permission issues (403s)
  • System warnings: Non-critical issues that might indicate future problems
  • Debug information: Detailed traces when troubleshooting specific issues

Error logs are written to /var/log/nginx/error.log by default and use severity levels (debug, info, notice, warn, error, crit, alert, emerg) to categorize messages.

Log Location and Permissions

On Ubuntu, Nginx logs are stored in /var/log/nginx/ by default and owned by the www-data user. The log directory requires proper permissions:

ls -la /var/log/nginx/

Output:

total 12
drwxr-x--- 2 www-data adm  4096 Feb  2 10:00 .
drwxrwxr-x 8 root     syslog 4096 Feb  2 09:45 ..
-rw-r----- 1 www-data adm     0 Feb  2 10:00 access.log
-rw-r----- 1 www-data adm     0 Feb  2 10:00 error.log

The adm group allows system administrators to read logs without root privileges. If you need to access logs as a regular user, add yourself to the adm group:

sudo usermod -aG adm your_username

Note: You’ll need to log out and back in for group changes to take effect.

Disk Space Considerations

Nginx logs can grow rapidly on busy servers. A single day of traffic on a moderately busy site might generate:

  • Access logs: 100-500 MB per day (varies with traffic)
  • Error logs: 10-50 MB per day (depends on error rate)

Without rotation, logs can consume gigabytes of disk space within weeks. The logrotate utility, covered later in this tutorial, automatically archives and compresses old logs to prevent disk space issues.

Understanding the error_log Directive

The error_log directive controls where and at what level Nginx writes error and diagnostic messages. Set the path and level (for example, warn) in your config; if you’re familiar with Apache, it behaves like Apache’s ErrorLog.

error_log Syntax

The error_log directive applies the following syntax:

# /etc/nginx/nginx.conf
error_log log_file log_level;

The log_file specifies the file where the logs will be written. The log_level specifies the lowest level of logging that you would like to record.

Logging Levels

The error_log directive can be configured to log more or less information as required. The level of logging can be any one of the following:

  • emerg: Emergency situations where the system is in an unusable state.
  • alert: Severe situations where action is needed promptly.
  • crit: Important problems that need to be addressed.
  • error: An error has occurred and something was unsuccessful.
  • warn: Something out of the ordinary happened, but is not a cause for concern.
  • notice: Something normal, but worth noting what has happened.
  • info: An informational message that might be nice to know.
  • debug: Debugging information that can be useful to pinpoint where a problem is occurring.

The levels higher on the list are considered a higher priority. If you specify a level, the log captures that level, and any level higher than the specified level.

For example, if you specify error, the log will capture messages labeled error, crit, alert, and emerg.

Performance Impact of Logging Levels

Different log levels have different performance implications:

  • warn (recommended for production): Minimal performance impact while preserving early warning signals such as upstream failures and configuration issues.
  • info and notice: Moderate performance impact, generates more I/O operations. Use when you need detailed operational visibility.
  • debug: Significant performance impact, generates extensive output. Only use for active troubleshooting, never leave enabled in production as it can slow your server and fill disk space rapidly.

To change the log level, edit your Nginx configuration:

An example of this directive in use is inside the main configuration file. Use your preferred text editor to access the following configuration file. This example uses nano:

sudo nano /etc/nginx/nginx.conf

Find the # Logging Settings section (usually in the lower part of the main block) and note the following directives:

# /etc/nginx/nginx.conf
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

If you do not want the error_log to log anything, you must send the output into /dev/null:

# /etc/nginx/nginx.conf
error_log /dev/null crit;

The other logging directive, access_log, will be discussed in the following section.

Understanding HttpLogModule Logging Directives

While the error_log directive is part of the core module, the access_log directive is part of the HttpLogModule. This provides the ability to customize the logs.

There are a few other directives included with this module that assist in configuring custom logs.

log_format Directive

The log_format directive is used to describe the format of a log entry using plain text and variables.

There is one format that comes predefined with Nginx called combined. This is a common format used by many servers.

The following is an example of the combined format if it was not defined internally and needed to be specified with the log_format directive:

# /etc/nginx/nginx.conf
log_format combined '$remote_addr - $remote_user [$time_local]  '
      '"$request" $status $body_bytes_sent '
      '"$http_referer" "$http_user_agent"';

This definition spans multiple lines until it finds the semicolon (;).

The lines beginning with a dollar sign ($) indicate variables, while the characters like -, [, and ] are interpreted literally.

The general syntax of the directive is:

# /etc/nginx/nginx.conf
log_format format_name 'string_describing_formatting';

You can use variables supported by the core module to formulate your logging strings.

JSON Log Format for Modern Monitoring

For integration with modern log aggregation tools like Elasticsearch, Logstash, Grafana, or Datadog, you can configure Nginx to output logs in JSON format. This makes parsing and querying logs much easier.

Add this custom log format to the http block in /etc/nginx/nginx.conf:

# /etc/nginx/nginx.conf
log_format json_combined escape=json
  '{'
    '"time_local":"$time_local",'
    '"remote_addr":"$remote_addr",'
    '"remote_user":"$remote_user",'
    '"request":"$request",'
    '"status": "$status",'
    '"body_bytes_sent":"$body_bytes_sent",'
    '"request_time":"$request_time",'
    '"http_referrer":"$http_referer",'
    '"http_user_agent":"$http_user_agent"'
  '}';

Then apply it to a specific server or location block:

# /etc/nginx/sites-available/your_site
server {
    listen 80;
    server_name example.com;

    access_log /var/log/nginx/example.com_access.log json_combined;

    # rest of your configuration
}

The escape=json parameter ensures that special characters are properly escaped, preventing JSON parsing errors.

Sending JSON logs to a monitoring pipeline: Once Nginx writes JSON to a file, you can ship it with a log agent without parsing. Examples: Filebeat or Fluentd to Elasticsearch; Promtail to Grafana Loki. Point the agent at /var/log/nginx/*.log (or per-site paths), set the input format to JSON, and configure retention and indexing in the destination. This keeps Nginx config simple and moves parsing and retention to the pipeline.

Custom Log Formats for Specific Needs

You can create multiple custom formats for different purposes. Here’s an example that tracks response times and upstream performance:

# /etc/nginx/nginx.conf
log_format performance '$remote_addr - $remote_user [$time_local] '
                       '"$request" $status $body_bytes_sent '
                       '"$http_referer" "$http_user_agent" '
                       'rt=$request_time uct="$upstream_connect_time" '
                       'uht="$upstream_header_time" urt="$upstream_response_time"';

This format adds:

  • $request_time: Total time to process the request
  • $upstream_connect_time: Time spent establishing connection to upstream server
  • $upstream_header_time: Time to receive headers from upstream
  • $upstream_response_time: Time to receive full response from upstream

These metrics are invaluable when troubleshooting slow application response times or identifying bottlenecks between Nginx and backend services.

Understanding the access_log Directive

The access_log directive uses similar syntax to the error_log directive, but is more flexible. It is used to configure custom logging.

The access_log directive uses the following syntax:

# /etc/nginx/nginx.conf
access_log /path/to/log/location [ format_name [ buffer=size ] ];

The default value for access_log is the combined format mentioned in the log_format section. You can use any format defined by a log_format definition.

The buffer size is the maximum size of data that Nginx will hold before writing it all to the log. You can also specify compression of the log file by adding gzip into the definition:

# /etc/nginx/nginx.conf
access_log /var/log/nginx/access.log combined gzip=1;

Unlike the error_log directive, if you do not want logging, you can turn it off by updating it in the configuration file:

# /etc/nginx/nginx.conf
##
# Logging Settings
##
access_log off;
error_log /var/log/nginx/error.log;

It is not necessary to write to /dev/null in this case.

Conditional Logging

You can use the if parameter with access_log to log only specific requests. This is useful for excluding health checks, static assets, or known bot traffic to reduce log volume:

# /etc/nginx/nginx.conf
# Map to determine if request should be logged
map $request_uri $loggable {
    ~^/health-check 0;
    ~^/ping 0;
    default 1;
}

server {
    listen 80;
    server_name example.com;

    # Only log if $loggable is 1
    access_log /var/log/nginx/access.log combined if=$loggable;
}

This configuration excludes /health-check and /ping endpoints from logs, which is particularly useful when using load balancers or monitoring systems that poll these endpoints frequently.

Separate Logs for Different Sites

When hosting multiple websites or applications, separate log files for each site makes troubleshooting and analysis much easier:

# /etc/nginx/sites-available/site1.conf
server {
    listen 80;
    server_name site1.example.com;

    access_log /var/log/nginx/site1_access.log combined;
    error_log /var/log/nginx/site1_error.log warn;

    # rest of configuration
}
# /etc/nginx/sites-available/site2.conf
server {
    listen 80;
    server_name site2.example.com;

    access_log /var/log/nginx/site2_access.log combined;
    error_log /var/log/nginx/site2_error.log warn;

    # rest of configuration
}

This approach isolates logs per site, making it easier to:

  • Track traffic patterns for individual applications
  • Identify which site is experiencing errors
  • Set different retention policies per site
  • Simplify log analysis with tools like goaccess or awstats

Managing Log Rotation

Rotate logs so they don’t fill the disk. Use Ubuntu’s built-in logrotate (configured in /etc/logrotate.d/nginx) or manually move logs and send kill -USR1 to the Nginx master process to reopen log files. Nginx does not rotate files itself but cooperates with rotation by reopening log file handles when it receives the USR1 signal.

Manual Log Rotation

To manually rotate your logs, you can create a script to rotate them. For example, move the current log to a new file for archiving. A common scheme is to name the most recent log file with a suffix of .0, and then name older files with .1, and so on:

mv /var/log/nginx/access.log /var/log/nginx/access.log.0

The command that actually reopens Nginx’s log files after you move them is kill -USR1 $(cat /run/nginx.pid). This does not kill the Nginx process; it sends a signal so Nginx reopens its log file handles. New requests are then logged to the new log file:

kill -USR1 $(cat /run/nginx.pid)

The /run/nginx.pid file is where Nginx stores the master process’s PID. It is specified at the top of the /etc/nginx/nginx.conf configuration file with the line that begins with pid:

sudo nano /etc/nginx/nginx.conf
# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
...

After the rotation, execute sleep 1 to allow the process to complete the transfer. You can then zip the old files or do whatever post-rotation processes you like:

sleep 1
gzip /var/log/nginx/access.log.0

Log Rotation with logrotate

The logrotate application is a program used to rotate logs. It is installed on Ubuntu by default, and Nginx on Ubuntu comes with a custom logrotate script.

Use your preferred text editor to access the rotation script. This example uses nano:

sudo nano /etc/logrotate.d/nginx

The first line of the file specifies the location that the subsequent lines will apply to. Keep this in mind if you switch the location of logging in the Nginx configuration files.

The rest of the file specifies that the logs will be rotated daily and that 14 older copies will be preserved.

Choose a retention policy that matches your environment:

  • Most production servers: daily with rotate 14 or rotate 30
  • Compliance / forensics: daily with rotate 90 (or more)
  • Very high traffic: rotate by size (for example, size 100M) or use hourly with a small rotate value

Notice that the postrotate section contains a command similar to the manual rotation mechanisms previously employed:

# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        [ ! -f /run/nginx.pid ] || kill -USR1 $(cat /run/nginx.pid)
    endscript
}

This section tells Nginx to reload the log files once the rotation is complete.

Best Practices for Nginx Logging

Most production logging problems come down to three things: too much noise, too much disk usage, or logs that are hard to query. The practices below focus on reducing I/O, keeping retention predictable, and improving troubleshooting speed.

1. Use Appropriate Log Levels for Each Environment

Different environments require different logging strategies:

Production:

# /etc/nginx/nginx.conf
error_log /var/log/nginx/error.log warn;

Use warn as a balanced production default. It captures early warning signals such as upstream failures and configuration issues without the noise and overhead of verbose debug logging.

Staging/QA:

# /etc/nginx/nginx.conf
error_log /var/log/nginx/error.log warn;

Include warnings to catch potential issues before they reach production. Using the same warn level as production helps validate real-world behavior before deployment.

Development:

# /etc/nginx/nginx.conf
error_log /var/log/nginx/error.log debug;

Use debug level for detailed troubleshooting, but never in production.

2. Implement Log Buffering for High-Traffic Sites

On busy servers, writing to disk on every request can become a performance bottleneck. Enable buffering:

# /etc/nginx/nginx.conf
access_log /var/log/nginx/access.log combined buffer=32k flush=5s;

This buffers up to 32KB of log data before writing to disk, or flushes every 5 seconds, whichever comes first. Benefits include:

  • Reduced disk I/O operations
  • Lower system call overhead
  • Improved request processing speed

Tradeoff: In the event of a crash, you might lose up to 5 seconds of log data.

3. Set Retention Policies Based on Compliance and Storage

Configure logrotate using the canonical /etc/logrotate.d/nginx stanza shown earlier, and adjust only rotate, size, and frequency (daily/hourly) based on your retention needs.

Key parameters explained:

  • rotate 14: Keep 14 days of logs (adjust based on your needs)
  • compress: Gzip old logs to save ~90% disk space
  • delaycompress: Don’t compress the most recent rotated log (useful for active analysis)
  • notifempty: Don’t rotate empty log files
  • create 0640 www-data adm: Set proper permissions on new log files

Storage planning: A site serving 1 million requests per day might generate:

  • Raw access logs: ~400MB/day
  • Compressed logs: ~40MB/day
  • 30-day retention: ~1.2GB total

4. Use Separate Logs for Security Analysis

Create a dedicated log for security-relevant events:

# /etc/nginx/nginx.conf
# Log format that includes more security-relevant fields
log_format security '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '$request_time $ssl_protocol $ssl_cipher';

server {
    listen 443 ssl;
    server_name example.com;

    # Regular access log
    access_log /var/log/nginx/access.log combined;

    # Security-focused log with additional fields
    access_log /var/log/nginx/security.log security;
}

This allows security tools to analyze a specialized log without processing all access logs, and includes SSL/TLS information useful for security audits.

5. Disable Logging for Static Assets (Optional)

To reduce log volume on sites serving many static files:

# /etc/nginx/nginx.conf
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
    access_log off;
    expires 30d;
}

Consideration: You’ll lose visibility into static asset requests. Only do this if:

  • Static assets are served from a CDN that provides separate analytics
  • You’ve confirmed these logs aren’t needed for business intelligence
  • Disk space or performance is a genuine concern

6. Centralize Logs for Multi-Server Deployments

For applications running across multiple Nginx instances, centralize logs using syslog:

# /etc/nginx/nginx.conf
access_log syslog:server=logserver.example.com:514,tag=nginx_access combined;
error_log syslog:server=logserver.example.com:514,tag=nginx_error warn;

Or ship logs to a dedicated logging service:

  • Elasticsearch + Filebeat: Parse JSON logs and visualize with Kibana
  • Logstash: Transform and route logs to multiple destinations
  • Cloud services: AWS CloudWatch, Google Cloud Logging, or Datadog

7. Monitor Log Disk Usage with Alerts

Set up monitoring to alert before logs fill your disk:

df -h /var/log/nginx/

Create a simple monitoring script:

# /usr/local/bin/check_log_disk.sh
#!/bin/bash
THRESHOLD=80
USAGE=$(df /var/log | awk 'NR==2 {print $5}' | sed 's/%//')

if [ "$USAGE" -gt "$THRESHOLD" ]; then
    echo "WARNING: /var/log disk usage is at ${USAGE}%" | mail -s "Log Disk Alert" admin@example.com
fi

Run this via cron every hour:

0 * * * * /usr/local/bin/check_log_disk.sh

Troubleshooting Common Logging Issues

Most Nginx logging issues fall into three buckets: logs not being written, log rotation not running, or disk usage growing unexpectedly. Use the checks below to confirm the root cause before changing configuration.

Issue 1: Logs Not Being Written

Symptoms: Log files exist but aren’t being updated, or don’t exist at all.

Diagnosis:

Check if Nginx is running:

sudo systemctl status nginx

Verify log file permissions:

ls -la /var/log/nginx/

Check Nginx error log for permission issues:

sudo tail -n 20 /var/log/nginx/error.log

Solutions:

If the directory doesn’t exist, create it:

sudo mkdir -p /var/log/nginx/
sudo chown www-data:adm /var/log/nginx/
sudo chmod 750 /var/log/nginx/

If the log file has wrong permissions:

sudo chown www-data:adm /var/log/nginx/*.log
sudo chmod 640 /var/log/nginx/*.log

If SELinux is enabled (CentOS/RHEL), set the correct context:

sudo semanage fcontext -a -t httpd_log_t "/var/log/nginx(/.*)?"
sudo restorecon -Rv /var/log/nginx/

Reload Nginx after fixing permissions:

sudo systemctl reload nginx

Issue 2: Log Rotation Not Working

Symptoms: Log files grow indefinitely; old logs aren’t compressed or removed.

Diagnosis:

Check if logrotate is installed:

which logrotate

View the Nginx logrotate configuration:

cat /etc/logrotate.d/nginx

Test logrotate manually in debug mode:

sudo logrotate -d /etc/logrotate.d/nginx

Check logrotate status:

sudo cat /var/lib/logrotate/status | grep nginx

Solutions:

If logrotate isn’t installed:

sudo apt update
sudo apt install logrotate

If the configuration has syntax errors, verify with debug mode output and fix any issues shown.

If rotation timing is wrong, check the main logrotate configuration:

cat /etc/cron.daily/logrotate

Force a rotation to verify it works:

sudo logrotate -f /etc/logrotate.d/nginx

If the postrotate script fails (Nginx not reloading logs), verify the PID file location:

cat /run/nginx.pid

Issue 3: Disk Space Filling Up Rapidly

Symptoms: Root partition fills up; logs consuming excessive disk space.

Diagnosis:

Check disk usage:

df -h /var/log

Find largest log files:

sudo du -sh /var/log/nginx/* | sort -h

Check log growth rate:

sudo watch -n 1 'ls -lh /var/log/nginx/access.log'

Solutions:

Implement more aggressive rotation:

# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    rotate 7
    size 100M
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        [ ! -f /run/nginx.pid ] || kill -USR1 $(cat /run/nginx.pid)
    endscript
}

Normalize logging verbosity in /etc/nginx/nginx.conf to avoid excessive disk usage:

# /etc/nginx/nginx.conf
error_log /var/log/nginx/error.log warn;

Disable access logging for static files (see Best Practices section above).

Enable conditional logging to exclude health checks and bots.

If logs are already too large, manually compress old logs:

sudo gzip /var/log/nginx/access.log.1
sudo gzip /var/log/nginx/error.log.1

Issue 4: Cannot Read Logs (Permission Denied)

Symptoms: tail or cat commands return “Permission denied” errors.

Solution:

Add your user to the adm group:

sudo usermod -aG adm $USER

Log out and back in, then verify:

groups

You should see adm in your group list. Now you can read logs without sudo:

tail -f /var/log/nginx/access.log

Issue 5: Log Format Causing Parsing Errors

Symptoms: Log analysis tools fail to parse logs; JSON format errors.

Diagnosis:

View recent log entries:

sudo tail -n 5 /var/log/nginx/access.log

If using JSON format, test with jq:

sudo tail -n 1 /var/log/nginx/access.log | jq .

Solutions:

Ensure escape=json is set in your log_format directive:

# /etc/nginx/nginx.conf
log_format json_combined escape=json
  '{'
    '"time":"$time_local",'
    # rest of format
  '}';

Test your log format by generating a request and immediately checking the output format.

For combined format issues, verify you haven’t modified the default format unexpectedly.

Issue 6: Nginx Restart/Reload Fails After Log Configuration Changes

Symptoms: Nginx fails to start after modifying logging directives.

Diagnosis:

Always test configuration before applying:

sudo nginx -t

Check systemd logs for specific errors:

sudo journalctl -xeu nginx

Common causes:

  • Invalid log format syntax: Missing quotes, unclosed braces, or invalid variables
  • Invalid file paths: Directory doesn’t exist or incorrect permissions
  • Conflicting directives: Multiple error_log or access_log directives at the same context level without understanding inheritance

Solution:

Review the error message from nginx -t, which typically points to the exact line and issue. Fix the syntax, verify permissions, then test again before reloading.

FAQ

Where are Nginx logs stored on Ubuntu?

By default, Nginx stores logs in /var/log/nginx/:

  • Access logs: /var/log/nginx/access.log
  • Error logs: /var/log/nginx/error.log

You can customize these locations in /etc/nginx/nginx.conf or in individual site configurations under /etc/nginx/sites-available/. Each server block can specify its own log files for easier management and analysis.

How often should Nginx logs be rotated?

The optimal rotation frequency depends on traffic volume and retention requirements:

  • Most production servers: Daily rotation with rotate 14 or rotate 30
  • High-traffic workloads: Rotate by size (for example, size 100M) or use hourly rotation with a small retention window
  • Compliance and audit requirements: Retain logs for 90 days or longer

Adjust the rotate, size, and frequency values in /etc/logrotate.d/nginx to match your operational and regulatory needs.

How do I create custom Nginx log formats?

Create custom formats using the log_format directive in the http block of /etc/nginx/nginx.conf:

log_format custom_format '$remote_addr - $remote_user [$time_local] '
                         '"$request" $status $body_bytes_sent '
                         'rt=$request_time';

Then apply it using access_log:

access_log /var/log/nginx/custom_access.log custom_format;

You can use any Nginx variables in your format. Common additions include $request_time, $upstream_response_time, $ssl_protocol, and $ssl_cipher for performance and security monitoring.

Why are my Nginx logs not rotating?

Common causes and solutions:

  1. Logrotate not installed: Install with sudo apt install logrotate
  2. Logrotate cron job disabled: Check /etc/cron.daily/logrotate exists and is executable
  3. Configuration errors: Test with sudo logrotate -d /etc/logrotate.d/nginx
  4. Nginx not releasing file handles: The postrotate script should send USR1 signal to reload logs
  5. Wrong file paths: Ensure paths in /etc/logrotate.d/nginx match actual log locations

Debug by running: sudo logrotate -dv /etc/logrotate.d/nginx to see detailed output of what logrotate would do.

Can Nginx log in JSON format?

Yes, Nginx can output logs in JSON format, which is ideal for modern log aggregation and analysis tools. Define a JSON format in /etc/nginx/nginx.conf:

log_format json_combined escape=json
  '{'
    '"timestamp":"$time_local",'
    '"client":"$remote_addr",'
    '"method":"$request_method",'
    '"uri":"$request_uri",'
    '"status":"$status",'
    '"bytes_sent":"$body_bytes_sent",'
    '"response_time":"$request_time",'
    '"user_agent":"$http_user_agent"'
  '}';

Apply it to your access log:

access_log /var/log/nginx/access.log json_combined;

The escape=json parameter ensures proper escaping of special characters. JSON logs work seamlessly with Elasticsearch, Splunk, Datadog, and other log analysis platforms.

How do I disable access logging for specific requests?

Use conditional logging with a map:

map $request_uri $loggable {
    ~^/health        0;
    ~^/status        0;
    ~*\.(gif|jpg|png)$ 0;
    default          1;
}

server {
    access_log /var/log/nginx/access.log combined if=$loggable;
}

This excludes health check endpoints and image requests from logs. Alternatively, disable logging completely for a location:

location /health-check {
    access_log off;
    return 200 "OK";
}

What’s the performance impact of verbose logging?

Logging performance impact varies by level and traffic:

  • warn level: Recommended default for production. Low overhead while preserving early warning signals.
  • info and notice levels: Moderate overhead due to higher disk I/O. Use temporarily for investigation.
  • debug level: High overhead and heavy disk writes. Use only during active troubleshooting and disable immediately after.

The actual impact depends on your disk I/O speed, traffic volume, and whether you use buffering. On high-traffic sites, enable buffering (buffer=32k flush=5s) to reduce disk writes and minimize performance overhead.

How long should I retain Nginx logs?

Log retention should balance compliance requirements, storage costs, and usefulness:

Minimum recommendations:

  • Production sites: 30 days for troubleshooting and trend analysis
  • Compliance requirements (PCI DSS, HIPAA, GDPR): 90 days to 1 year
  • Forensic/security needs: 90+ days for incident investigation

Storage efficiency:

  • Use compress in logrotate to reduce storage by ~90%
  • Archive older logs to cheaper storage (S3 Glacier, etc.)
  • Use tiered retention: 30 days local, 90 days archived, 1 year cold storage

Example retention policy:

/var/log/nginx/*.log {
    daily
    rotate 30           # 30 days local
    compress
    dateext
    # Archive anything older to S3 via a script
    lastaction
        /usr/local/bin/archive_old_logs.sh
    endscript
}

Conclusion

Proper log configuration and management directly affect your ability to troubleshoot outages, detect abnormal traffic, and prevent disk-related failures. By implementing the logging strategies covered in this tutorial, you gain practical visibility into real server behavior.

You’ve learned how to configure both error_log and access_log directives, create custom log formats for specific monitoring needs, implement log rotation to prevent disk space issues, and troubleshoot common logging problems. These capabilities will help you diagnose issues quickly, understand traffic patterns, and maintain server security.

For next steps, consider:

Remember that effective logging is about finding the right balance: comprehensive enough to catch problems, but not so verbose that it impacts performance or overwhelms your storage. Start with the defaults, monitor your actual usage, and adjust based on your specific needs.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the author(s)

Justin Ellingwood
Justin Ellingwood
Author
See author profile

Former Senior Technical Writer at DigitalOcean, specializing in DevOps topics across multiple Linux distributions, including Ubuntu 18.04, 20.04, 22.04, as well as Debian 10 and 11.

Vinayak Baranwal
Vinayak Baranwal
Editor
Technical Writer II
See author profile

Building future-ready infrastructure with Linux, Cloud, and DevOps. Full Stack Developer & System Administrator. Technical Writer @ DigitalOcean | GitHub Contributor | Passionate about Docker, PostgreSQL, and Open Source | Exploring NLP & AI-TensorFlow | Nailed over 50+ deployments across production environments.

Still looking for an answer?

Was this helpful?


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

This is pretty essential for any proper web server imo. I’ve put my site logs in /var/log/nginx/ and just symlink them to their appropriate logs folders. Really helps if you don’t regularly log into your server and check log sizes, I’ve been screwed over by gigantic logs a few times.

This comment has been deleted

I have Logrotate for Nginx log rotation. Log rotation from your instruction works fine except one thing - after rotation the nginx stiil write to “access.log.1” file instead “access.log” Ubuntu 14, Nginx 1.8 Do you have any suggestions to resolve issue?

Please update the article for logrotate.d. How this operates has been updated since the article was written in 2013.

where is the config file for logrotate nginx

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.