Nginx, like most applications, records a wealth of data
related to client interactions, system events, and potential errors. However,
this data's potential can only be fully realized through proper configuration,
management, and analysis.
This article will teach you to effectively customize Nginx logs for enhanced
visibility and control over your web servers and proxies. Doing this will help you
proactively identify and address issues before they disrupt user experience.
Let's begin!
Prerequisites
To follow through with this tutorial, you need the following:
Basic command-line skills.
A Linux system that includes a non-root user with sudo privileges.
A recent version of Docker
installed on your system.
Step 1 — Running Nginx with Docker
Using the official Docker image is the easiest
way to begin working with the Nginx. It simplifies the setup process and ensures
consistent reproducibility across different systems.
To get started, create a new nginx-logging-tutorial directory, navigate into
it, and execute the following docker run command: :
Copied!
mkdir nginx-logging-tutorial && cd nginx-logging-tutorial
Copied!
docker run --name nginx-server --rm -p 80:80 nginx
In this command:
--name nginx-server: Assigns the nginx-server name to the container for
easier reference.
--rm: Automatically removes the container when it's stopped, ideal for
testing or temporary setups.
-p 80:80: Maps port 80 of your host machine to port 80 inside the container,
allowing you to access the Nginx server at http://localhost.
If the nginx image isn't already present on your system, Docker will download
it before launching the container.
If you encounter an error like the following:
Output
docker: Error response from daemon: driver failed programming external connectivity on endpoint nginx (363e1b33c95786ca6293208b529051a4cf0509208444707b65aef2ddd329ef7e): Bind for 0.0.0.0:80
failed: port is already allocated.
It indicates that another application on your system is already using port 80.
Ensure that no other services are running on this port before retrying the
command.
If Nginx starts successfully, you'll see messages like these in your terminal:
Output
. . .
/docker-entrypoint.sh: Configuration complete; ready for start up
2025/05/27 14:47:59 [notice] 1#1: using the "epoll" event method
2025/05/27 14:47:59 [notice] 1#1: nginx/1.27.0
2025/05/27 14:47:59 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14)
2025/05/27 14:47:59 [notice] 1#1: OS: Linux 6.9.12-200.fc40.x86_64
2025/05/27 14:47:59 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1073741816:1073741816
2025/05/27 14:47:59 [notice] 1#1: start worker processes
2025/05/27 14:47:59 [notice] 1#1: start worker process 29
Now, open your web browser and navigate to http://localhost. You should be
greeted by the default Nginx welcome page, confirming that your web server is up
and running.
Upon returning to your terminal, you might notice log entries like these:
These logs reveal that your browser successfully fetched the main webpage but
encountered a 404 error when attempting to retrieve the favicon.ico file
because it's not in the default Nginx directory.
Feel free to stop the Nginx container using Ctrl-C before moving on to the
next section, where we'll dive deeper into the meaning of these log messages.
Step 2 — Locating the Nginx log files
Like most web servers, Nginx meticulously records its activities in two distinct
log files:
Access log: This file chronicles each incoming request, capturing crucial
details such as the client's IP address, the timestamp of the request, the
requested resource (URI), the HTTP status code of the response, and the
client's user agent (browser and operating system).
Error log: This file serves as a diagnostic tool, recording errors and
issues encountered during request processing and other Nginx operations. It
logs information such as the timestamp, error level, error message, and any
relevant context to aid troubleshooting.
Locating Nginx logs in different environments
The location of these log files varies depends on your operating system and
Nginx installation method.
Linux distributions
In most Linux distributions, Nginx log files are typically located in the
/var/log/nginx/ directory. You'll find them named access.log and error.log
respectively.
If you can't find the log files in the default location, you'll need to check
your specific Nginx configuration. Start by determining the location of your
Nginx configuration file (usually /etc/nginx/nginx.conf):
Copied!
sudo nginx -t
This command should output the location of your configuration file if it's
valid:
Output
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Open the configuration file and look for the error_log and access_log
directives to pinpoint their respective locations.
By default, Nginx applies the error_log directive globally, while the
access_log is usually placed within the http block.
You can use the tail command to view the contents of these files in real-time:
Copied!
sudo tail -f /var/log/nginx/access.log
Copied!
sudo tail -f /var/log/nginx/error.log
Docker container
Since Docker containers are ephemeral, it's not practical to store logs directly
within the container. The official Nginx Docker image addresses this by creating
symbolic links from /var/log/nginx/access.log and /var/log/nginx/error.log
to the container's standard output (/dev/stdout) and standard error
(/dev/stderr) streams respectively. This enables Docker's logging
mechanisms to collect and manage the logs.
Similarly, to view only the error logs, redirect the standard output to
/dev/null:
Copied!
docker logs -f nginx-server 1>/dev/null
Output
. . .
2025/05/27 16:37:59 [error] 31#31: *3 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost", referrer: "http://localhost/"
Now that you know how to locate and access Nginx log files in various
environments, let's explore how you can customize the access log format to suit
your needs.
Step 3 — Configuring Nginx access logs
By default, Nginx access logs are generated in the combined format unless
otherwise specified. This format is defined as:
Let's break down what each token in the log represents:
172.17.0.1: The IP address of the client that made the request.
-: If authentication is used, this is the authenticated username; otherwise,
it's a hyphen (-).
[27/May/2025:16:37:59 +0000]: The local time when the request was processed.
"GET / HTTP/1.1" : The request method, path, and HTTP protocol version.
200: The HTTP status code returned to the client.
615: The size of the response body in bytes.
"-": The URL of the referring page (if any).
"Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0" -
The browser and operating system information provided by the client.
"-": If the request passed through a proxy, this variable contains the
original client IP address.
While this format captures a wide range of information that is useful for
analyzing traffic patterns, troubleshooting errors, and understanding user
behavior, you may want to customize it to capture only the data that matters
most to you. Let's look at how to do that next.
You can tailor the access log format using the log_format directive in either
the main Nginx configuration file (/etc/nginx/nginx.conf) or within
host-specific configurations in /etc/nginx/sites-enabled.
If Nginx is running directly on your host machine, you can edit the relevant
file accordingly. For Docker instances, execute the command below to extract the
Nginx configuration file from the nginx image and save it to an nginx.conf
file on your host machine:
Copied!
docker run --rm --entrypoint=cat nginx /etc/nginx/nginx.conf > nginx.conf
Once you're ready to launch the container once again, ensure to mount the
modified file from your host machine to /etc/nginx/nginx.conf within the
container:
Copied!
docker run --name nginx-server -v ./nginx.conf:/etc/nginx/nginx.conf:ro -d nginx
Monitor Nginx containers with Better Stack's eBPF collector
Running Nginx in Docker? Better Stack provides eBPF-based observability that automatically captures logs, metrics, and network traces from your containers—no code changes required.
Get instant visibility into request latency, HTTP errors, and service dependencies with our remotely controlled collector. Forward logs using Vector or connect directly via OpenTelemetry.
Predictable pricing starting at $0.25/GB. Start free in minutes.
Customizing the access log format
Customizing the format of the entries in the access log can be done using the
log_format directive:
Copied!
log_format <name> '<formatting_variables>';
All you need to do is give the custom format a name and define the structure of
the log using the provided
core variables
and
log variables.
Here's an example of what it could look like:
Nginx's access logs can become quite large, especially under heavy traffic.
Conditional logging allows you to selectively filter log entries based on
specific criteria to reduce log volume and improve performance.
The <condition> is a boolean expression that Nginx evaluates for each request.
If it evaluates to true, the log entry is written; otherwise, it's skipped.
The following example demonstrates how to exclude successful (2xx) and
redirection (3xx) status codes from the access log:
Copied!
http {
map $status $loggable {
~^[23] 0; # Match 2xx and 3xx status codes
default 1; # Log everything else
}
access_log /var/log/nginx/access.log combined if=$loggable;
}
Some practical applications of conditional logging include:
Logging only error responses (4xx and 5xx) for troubleshooting.
Excluding specific user agents or IP addresses known to be bots.
Logging only requests to specific parts of your application.
If you're already collecting request logs through your web application, you may
want to disable the Nginx access log by using a special off value, or by
redirecting to /dev/null
Copied!
access_log off;
access_log /dev/null;
Step 4 - Structuring Nginx access logs
In the world of cloud-native distributed systems and microservices, structured
logging has gained significant traction due to its numerous
benefits over traditional plain-text logs.
For example, Caddy, an Nginx alternative, produces access logs that
look like this:
Let's explore how to bring Nginx access logs into this modern era.
While Nginx doesn't natively produce JSON logs, you can achieve this using the
log_format directive in conjunction with the escape=json parameter which
ensures that characters that aren't valid in JSON are properly escaped.
This structured format makes your logs much easier to parse and analyze, opening
the doors to powerful log management and visualization capabilities.
Step 5 — Configuring Nginx error logs
The Nginx error log is a crucial tool for diagnosing and resolving issues with
your web server. It captures errors, warnings, and other important events that
occur during various Nginx operations. Let's explore how to configure and manage
this valuable resource.
The error_log directive controls Nginx's error logging behavior. It accepts
two parameters: the path of the log file, and the minimum severity
level of the log.
Nginx categorizes error log messages into the following levels, ranging from
least to most severe:
debug: Highly detailed messages primarily used for troubleshooting and
development.
info: General informational messages about the server's operation.
notice: Noteworthy events that aren't necessarily errors.
warn: Unexpected occurrences that could indicate potential problems.
error: Actual errors encountered during processing.
crit: Critical conditions that require attention.
alert: Errors that demand immediate action.
emerg: Severe errors that render the system unusable.
If you haven't explicitly configured the error severity level in your Nginx
configuration, you will see messages at the error level and all levels above
it (crit, alert, and emerg). However, the default level for the official
Nginx docker image is set to notice.
To change the default error log level, provide the desired level as the second
parameter to the error_log directive:
Copied!
error_log /var/log/nginx/error.log warn;
This configuration will log messages at the warn level and all higher levels.
The Nginx error log format
Nginx error logs adhere to a format designed for human readability and easy
parsing by tools. The general format is:
In this setup, all events except debug level messages will be logged to
error.log, while emergency events will be logged to a separate file named
emerg_error.log.
Disabling the error log
If you need to completely disable the Nginx error log (though not generally
recommended), you can redirect it to /dev/null. There doesn't appear to be a
special off value at the time of writing.
Copied!
error_log /dev/null;
In the next section, we'll look at how you can structure your Nginx error logs
in JSON format.
Step 7 — Structuring Nginx error logs
While Nginx doesn't offer built-in JSON formatting for its error logs, we can
leverage external log processing tools like Logstash,
Fluentd, or Vector to parse, reformat,
and enrich these logs for better analysis and integration with modern logging
systems.
In this section, I'll demonstrate how to use Vector to transform Nginx error
logs into structured JSON format, providing similar benefits to what we achieved
with the access logs in the previous step.
To see this in action, let's bring up both the Nginx and Vector containers with
Docker Compose. Before you proceed, create a
docker-compose.yml file in your nginx-logging-tutorial directory and
populate it with the contents below:
When configuring the vector service in your docker-compose.yml, it's necessary
to mount the vector.yaml file you created into the container to replace the
default configuration
You also need to mount the Docker socket (/var/run/docker.sock) inside the
container. This gives Vector the necessary access to communicate with the Docker
daemon, enabling it to identify and monitor the logs of other containers you
specify.
However, in a production environment, exposing the Docker socket directly to a
container can be a security risk. Consider
alternative approaches, such as:
Configuring Vector to interact with the Docker daemon over a SSH or HTTPS.
Instead of running Vector in a container, install it directly on the host
machine to avoid the need for socket mounting.
These precautions help safeguard your Docker environment while still allowing
Vector to effectively collect and process your Nginx logs.
To bring up your services, ensure that you've removed the running nginx-server
container by pressing CTRL-C before running the command below:
Copied!
docker compose up -d
Output
[+] Running 3/3
✔ Network nginx-logging-tutorial_default Created 0.3s
✔ Container nginx-server Started 0.6s
✔ Container vector Started 0.6s
Navigate back to your browser, access http://localhost and refresh the page a
couple of times.
You can then view the processed logs with:
Copied!
docker logs -f vector
If you have jq installed, you can pipe the
output to jq while ignoring non-JSON objects with:
The context property contains the original JSON object from Nginx as parsed by
the parse_json() function we used earlier. The other properties were added by
Vector when collecting the logs from the Docker container.
To what see a parsed error log, you can make a request for a non-existent file
with:
Copied!
curl http://localhost/favicon.ico
You will subsequently observe the following entry in the Docker logs:
With structured JSON logs for both access and error events, you're now
well-equipped to gain valuable insights into your Nginx server's behavior and
performance.
Now that you've configured and structured your Nginx logs, let's elevate your
log management by centralizing them with a dedicated service.
This will empower you to view, search, and analyze your logs effortlessly,
uncovering valuable insights and identifying potential issues.
Better Stack is an excellent solution for
centralized log management. It offers a user-friendly interface, powerful search
capabilities, and the ability to set up alerts for critical events.
There are multiple approaches to send your Nginx logs to Better Stack:
Using the Better Stack Collector (recommended for Docker/Kubernetes): Automatically collect logs, traces, and metrics from your containers with zero code changes via eBPF and auto-instrumentation.
Using Vector as a log forwarder: Parse and transform logs before sending them to Better Stack (demonstrated below).
The video below demonstrates how the Better Stack Collector works with containerized applications, showing the automatic log collection process:
For this tutorial, we'll demonstrate the Vector approach since it allows you to see how log transformation works. However, if you're running Nginx in a Kubernetes cluster or need to collect metrics and traces alongside logs, the Better Stack Collector is the simpler option.
Let's see how to integrate your Nginx logs with Better Stack using Vector as a log
forwarder.
Sign up for a free Better Stack
account, then navigate to the Logs & Metrics dashboard. From the left-hand
menu, choose Sources and click on Connect source:
Name your source Nginx Logging Tutorial and choose Vector as the platform,
then click Create source:
Upon creating the source, you’ll receive a source token (e.g., qU73jvQjZrNFHimZo4miLdxF) and an ingestion host (e.g., s1315908.eu-nbg-2.betterstackdata.com). Be sure to copy both—these are essential for configuring log forwarding:
Return to your editor, and update your vector.yaml file with the following configuration, replacing <your_betterstack_source_token> and <your_ingesting_host> with the values you copied:
Instead of printing the processed logs to the console, this updated
configuration instructs Vector to transmit them into Better Stack over HTTP.
You may now restart your running Docker Compose services with:
Copied!
docker compose restart
Output
[+] Restarting 2/2
✔ Container nginx-server Started 0.8s
✔ Container vector Started 0.8s
Once the services are relaunched, return back to your browser, access
http://localhost and refresh the homepage a couple of times.
Return to your Better Stack tab and access the Live tail view by clicking the link under your source name, or select Live tail from the left menu and use the dropdown filter at the top left to view your Nginx logs:
After a few seconds, you'll start seeing the Nginx logs arriving in Live
tail:
You can click on any log entry to view its detailed properties and JSON structure:
To explore Live tail's advanced features for filtering, searching, and analyzing your logs, watch this walkthrough:
Congratulations! You've successfully centralized your Nginx logs with Better
Stack. You now have a powerful platform to monitor, search, and analyze your log
data, helping you keep your web server and applications running smoothly and
efficiently.
Feel free to explore the various
features and capabilities of Better
Stack to gain deeper insights into your Nginx logs and application behavior.
Final thoughts
In this guide, you learned how to locate, access, and interpret Nginx access and
error logs, both on traditional servers and within Docker containers. You also
learned to customize access logs, implement conditional logging, and format logs
as structured JSON for easier analysis.
By following these techniques, you can now effectively monitor your Nginx
server, troubleshoot issues, and gain valuable insights into your web traffic
and application performance.