Tools: How To Configure Logging and Log Rotation in Nginx on Ubuntu
Source: DigitalOcean
By Justin Ellingwood and Vinayak Baranwal 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. Use this cheat sheet to validate changes quickly: To follow this tutorial, you will need: With Nginx running on your Ubuntu server, you’re ready to begin. If you want a safe default you can deploy immediately, start here and refine later: Then apply per-site logging in your server {} block (commonly in /etc/nginx/sites-available/your_site): This configuration balances signal quality, disk I/O, and operational safety for most production Ubuntu deployments. Nginx uses two main logs: access (for every request) and error (for diagnostics). Each serves a distinct purpose in monitoring and troubleshooting. Use these placement rules to avoid unexpected overrides: 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 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: 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 capture diagnostic information when something goes wrong, from minor warnings to critical system failures. They include: 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. 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: 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: Note: You’ll need to log out and back in for group changes to take effect. Nginx logs can grow rapidly on busy servers. A single day of traffic on a moderately busy site might generate: 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. 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. The error_log directive applies the following syntax: 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. 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: 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. Different log levels have different performance implications: 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: Find the # Logging Settings section (usually in the lower part of the main block) and note the following directives: If you do not want the error_log to log anything, you must send the output into /dev/null: The other logging directive, access_log, will be discussed in the following section. 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. 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: 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: You can use variables supported by the core module to formulate your logging strings. 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: Then apply it to a specific server or location block: 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. You can create multiple custom formats for different purposes. Here’s an example that tracks response times and upstream performance: These metrics are invaluable when troubleshooting slow application response times or identifying bottlenecks between Nginx and backend services. 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: 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: Unlike the error_log directive, if you do not want logging, you can turn it off by updating it in the configuration file: It is not necessary to write to /dev/null in this case. 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: 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. When hosting multiple websites or applications, separate log files for each site makes troubleshooting and analysis much easier: This approach isolates logs per site, making it easier to: 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. 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: 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: 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: 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: 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: 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: Notice that the postrotate section contains a command similar to the manual rotation mechanisms previously employed: This section tells Nginx to reload the log files once the rotation is complete. 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. Different environments require different logging strategies: 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. Include warnings to catch potential issues before they reach production. Using the same warn level as production helps validate real-world behavior before deployment. Use debug level for detailed troubleshooting, but never in production. On busy servers, writing to disk on every request can become a performance bottleneck. Enable buffering: This buffers up to 32KB of log data before writing to disk, or flushes every 5 seconds, whichever comes first. Benefits include: Tradeoff: In the event of a crash, you might lose up to 5 seconds of log data. 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: Storage planning: A site serving 1 million requests per day might generate: Create a dedicated log for security-relevant events: This allows security tools to analyze a specialized log without processing all access logs, and includes SSL/TLS information useful for security audits. To reduce log volume on sites serving many static files: Consideration: You’ll lose visibility into static asset requests. Only do this if: For applications running across multiple Nginx instances, centralize logs using syslog: Or ship logs to a dedicated logging service: Set up monitoring to alert before logs fill your disk: Create a simple monitoring script: Run this via cron every hour: 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. Symptoms: Log files exist but aren’t being updated, or don’t exist at all. Check if Nginx is running: Verify log file permissions: Check Nginx error log for permission issues: If the directory doesn’t exist, create it: If the log file has wrong permissions: If SELinux is enabled (CentOS/RHEL), set the correct context: Reload Nginx after fixing permissions: Symptoms: Log files grow indefinitely; old logs aren’t compressed or removed. Check if logrotate is installed: View the Nginx logrotate configuration: Test logrotate manually in debug mode: Check logrotate status: If logrotate isn’t installed: 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: Force a rotation to verify it works: If the postrotate script fails (Nginx not reloading logs), verify the PID file location: Symptoms: Root partition fills up; logs consuming excessive disk space. Find largest log files: Check log growth rate: Implement more aggressive rotation: Normalize logging verbosity in /etc/nginx/nginx.conf to avoid excessive disk usage: 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: Symptoms: tail or cat commands return “Permission denied” errors. Add your user to the adm group: Log out and back in, then verify: You should see adm in your group list. Now you can read logs without sudo: Symptoms: Log analysis tools fail to parse logs; JSON format errors. View recent log entries: If using JSON format, test with jq: Ensure escape=json is set in your log_format directive: 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. Symptoms: Nginx fails to start after modifying logging directives. Always test configuration before applying: Check systemd logs for specific errors: 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. By default, Nginx stores logs in /var/log/nginx/: 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. The optimal rotation frequency depends on traffic volume and retention requirements: Adjust the rotate, size, and frequency values in /etc/logrotate.d/nginx to match your operational and regulatory needs. Create custom formats using the log_format directive in the http block of /etc/nginx/nginx.conf: Then apply it using access_log: 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. Common causes and solutions: Debug by running: sudo logrotate -dv /etc/logrotate.d/nginx to see detailed output of what logrotate would do. 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: Apply it to your access log: The escape=json parameter ensures proper escaping of special characters. JSON logs work seamlessly with Elasticsearch, Splunk, Datadog, and other log analysis platforms. Use conditional logging with a map: This excludes health check endpoints and image requests from logs. Alternatively, disable logging completely for a location: Logging performance impact varies by level and traffic: 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. Log retention should balance compliance requirements, storage costs, and usefulness: Minimum recommendations: Example retention policy: 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 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. 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. 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 Please complete your information! Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation. Full documentation for every DigitalOcean product. The Wave has everything you need to know about building a business, from raising funding to marketing your product. Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter. New accounts only. By submitting your email you agree to our Privacy Policy Scale up as you grow — whether you're running one virtual machine or ten thousand. Sign up and get $200 in credit for your first 60 days with DigitalOcean.* *This promotional offer applies to new accounts only.