How to Reload Nginx Configuration Periodically with Docker
Learn how to create a bash script to periodically reload Nginx inside a Docker container. This is useful for handling periodic SSL certificate updates.
Table of Contents 📖
- How to Reload Nginx Periodically
- Writing the Script
- Building the Docker Image and Container
- Testing the Program
How to Reload Nginx Periodically
To get Nginx to reload periodically we can create a script that runs an infinite loop in the background. This loop will periodically reload Nginx. We run this process in the background so that we can run Nginx in the foreground.
INFO: When performing a reload, the Nginx master process first checks the configuration file syntax and then tries to apply the new configuration. If successful, it starts new worker processes and sends messages to old worker processes requesting them to shut down gracefully.
Writing the Script
We can create an infinite loop with the following syntax.
while true;
As true is always true, we will create an infinite loop. However, we don't want the loop to run every millisecond. We want the script to pause for a period of time. We can do that with the sleep command.
while true; do
sleep 10s;
Now we will sleep for 10 seconds. After the 10 seconds are up, we want to reload Nginx with its new configuration.
while true; do
sleep 10s;
nginx -s reload;
done
INFO: The -s flag signals to the Nginx master process. Here we are sending a SIGHUP signal to reload the configuration.
Now we need to make it so this command runs in the background. We can run a process in the background by placing the & symbol after it.
while true; do
sleep 10s;
nginx -s reload;
done &
We run this process in the background as we want our foreground process to be Nginx.
while true; do
sleep 10s;
nginx -s reload;
done &
exec nginx -g 'daemon off;'
The exec command is used to replace the current shell process with a new one. This ensures that the Nginx process is the main process and not a child process. Specifying daemon off tells Nginx to stay in the foreground. In other words, not to detach from the terminal. We want to do this because we want Nginx to be the main process of our Docker container.
Building the Docker Image and Container
Now lets build our Docker image and container. First lets create our Dockerfile.
FROM nginx:1.25-alpine
WORKDIR /usr/local/bin
COPY reload.sh .
RUN chmod +x reload.sh
ENTRYPOINT reload.sh
- Use Nginx version 1.25 Alpine as the base image.
- Set the working directory to /usr/local/bin. This location is often used to store scripts for local users.
- Copy our Nginx reload script to the working directory.
- Make the script executable.
- Run the script when the container starts.
Now lets create a simple Nginx configuration to update and demonstrate our reload script is working.
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '[$time_local] "$request" URI - $uri';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
listen [::]:80;
server_name localhost;
root /usr/share/nginx/html;
location / {
try_files $uri =404;
}
}
}
We will copy this configuration into Nginx using a volume. Lets now build our Docker image and create a container from it.
docker build -t nginx-sh . && docker run --name nginx-sh-c -p 6464:80 -v ./nginx.conf:/etc/nginx/nginx.conf nginx-sh
Now lets check the logs to see the configuration reload is working every 10 seconds.
2024/05/16 13:56:13 [notice] 1#1: using the "epoll" event method
2024/05/16 13:56:13 [notice] 1#1: nginx/1.25.5
2024/05/16 13:56:13 [notice] 1#1: built by gcc 13.2.1 20231014 (Alpine 13.2.1_git20231014)
2024/05/16 13:56:13 [notice] 1#1: OS: Linux 5.15.49-linuxkit-pr
2024/05/16 13:56:13 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2024/05/16 13:56:13 [notice] 1#1: start worker processes
2024/05/16 13:56:13 [notice] 1#1: start worker process 9
2024/05/16 13:56:13 [notice] 1#1: start worker process 10
2024/05/16 13:56:13 [notice] 1#1: start worker process 11
2024/05/16 13:56:13 [notice] 1#1: start worker process 12
2024/05/16 13:56:13 [notice] 1#1: start worker process 13
2024/05/16 13:56:13 [notice] 1#1: start worker process 14
2024/05/16 13:56:23 [notice] 15#15: signal process started
2024/05/16 13:56:23 [notice] 1#1: signal 1 (SIGHUP) received from 15, reconfiguring
2024/05/16 13:56:23 [notice] 1#1: reconfiguring
2024/05/16 13:56:23 [notice] 1#1: using the "epoll" event method
2024/05/16 13:56:23 [notice] 1#1: start worker processes
2024/05/16 13:56:23 [notice] 1#1: start worker process 17
2024/05/16 13:56:23 [notice] 1#1: start worker process 18
2024/05/16 13:56:23 [notice] 1#1: start worker process 19
2024/05/16 13:56:23 [notice] 1#1: start worker process 20
2024/05/16 13:56:23 [notice] 1#1: start worker process 21
2024/05/16 13:56:23 [notice] 1#1: start worker process 22
2024/05/16 13:56:23 [notice] 9#9: gracefully shutting down
2024/05/16 13:56:23 [notice] 13#13: gracefully shutting down
2024/05/16 13:56:23 [notice] 13#13: exiting
2024/05/16 13:56:23 [notice] 9#9: exiting
2024/05/16 13:56:23 [notice] 10#10: gracefully shutting down
2024/05/16 13:56:23 [notice] 13#13: exit
2024/05/16 13:56:23 [notice] 9#9: exit
2024/05/16 13:56:23 [notice] 10#10: exiting
2024/05/16 13:56:23 [notice] 10#10: exit
2024/05/16 13:56:23 [notice] 14#14: gracefully shutting down
2024/05/16 13:56:23 [notice] 12#12: gracefully shutting down
2024/05/16 13:56:23 [notice] 12#12: exiting
2024/05/16 13:56:23 [notice] 11#11: gracefully shutting down
2024/05/16 13:56:23 [notice] 11#11: exiting
2024/05/16 13:56:23 [notice] 12#12: exit
2024/05/16 13:56:23 [notice] 11#11: exit
2024/05/16 13:56:23 [notice] 14#14: exiting
2024/05/16 13:56:23 [notice] 14#14: exit
2024/05/16 13:56:23 [notice] 1#1: signal 17 (SIGCHLD) received from 10
2024/05/16 13:56:23 [notice] 1#1: worker process 9 exited with code 0
2024/05/16 13:56:23 [notice] 1#1: worker process 10 exited with code 0
2024/05/16 13:56:23 [notice] 1#1: worker process 11 exited with code 0
2024/05/16 13:56:23 [notice] 1#1: worker process 12 exited with code 0
2024/05/16 13:56:23 [notice] 1#1: signal 29 (SIGIO) received
2024/05/16 13:56:23 [notice] 1#1: signal 17 (SIGCHLD) received from 13
2024/05/16 13:56:23 [notice] 1#1: worker process 13 exited with code 0
2024/05/16 13:56:23 [notice] 1#1: signal 29 (SIGIO) received
2024/05/16 13:56:23 [notice] 1#1: signal 17 (SIGCHLD) received from 14
2024/05/16 13:56:23 [notice] 1#1: worker process 14 exited with code 0
2024/05/16 13:56:23 [notice] 1#1: signal 29 (SIGIO) received
2024/05/16 13:56:33 [notice] 23#23: signal process started
2024/05/16 13:56:33 [notice] 1#1: signal 1 (SIGHUP) received from 23, reconfiguring
2024/05/16 13:56:33 [notice] 1#1: reconfiguring
2024/05/16 13:56:33 [notice] 1#1: using the "epoll" event method
2024/05/16 13:56:33 [notice] 1#1: start worker processes
2024/05/16 13:56:33 [notice] 1#1: start worker process 25
2024/05/16 13:56:33 [notice] 1#1: start worker process 26
2024/05/16 13:56:33 [notice] 1#1: start worker process 27
2024/05/16 13:56:33 [notice] 1#1: start worker process 28
2024/05/16 13:56:33 [notice] 1#1: start worker process 29
2024/05/16 13:56:33 [notice] 1#1: start worker process 30
2024/05/16 13:56:33 [notice] 17#17: gracefully shutting down
2024/05/16 13:56:33 [notice] 18#18: gracefully shutting down
2024/05/16 13:56:33 [notice] 19#19: gracefully shutting down
2024/05/16 13:56:33 [notice] 21#21: gracefully shutting down
2024/05/16 13:56:33 [notice] 20#20: gracefully shutting down
2024/05/16 13:56:33 [notice] 21#21: exiting
2024/05/16 13:56:33 [notice] 20#20: exiting
2024/05/16 13:56:33 [notice] 19#19: exiting
2024/05/16 13:56:33 [notice] 18#18: exiting
2024/05/16 13:56:33 [notice] 21#21: exit
2024/05/16 13:56:33 [notice] 20#20: exit
2024/05/16 13:56:33 [notice] 19#19: exit
2024/05/16 13:56:33 [notice] 18#18: exit
2024/05/16 13:56:33 [notice] 17#17: exiting
2024/05/16 13:56:33 [notice] 17#17: exit
2024/05/16 13:56:33 [notice] 22#22: gracefully shutting down
2024/05/16 13:56:33 [notice] 22#22: exiting
2024/05/16 13:56:33 [notice] 22#22: exit
2024/05/16 13:56:33 [notice] 1#1: signal 17 (SIGCHLD) received from 17
2024/05/16 13:56:33 [notice] 1#1: worker process 17 exited with code 0
2024/05/16 13:56:33 [notice] 1#1: worker process 18 exited with code 0
2024/05/16 13:56:33 [notice] 1#1: worker process 19 exited with code 0
2024/05/16 13:56:33 [notice] 1#1: worker process 20 exited with code 0
2024/05/16 13:56:33 [notice] 1#1: signal 29 (SIGIO) received
2024/05/16 13:56:33 [notice] 1#1: signal 17 (SIGCHLD) received from 21
2024/05/16 13:56:33 [notice] 1#1: worker process 21 exited with code 0
2024/05/16 13:56:33 [notice] 1#1: worker process 22 exited with code 0
2024/05/16 13:56:33 [notice] 1#1: signal 29 (SIGIO) received
2024/05/16 13:56:33 [notice] 1#1: signal 17 (SIGCHLD) received from 22
INFO: Check the logs for the presence of the SIGHUP signal. This is the signal sent to Nginx telling it to reload the configuration.
Testing the Program
Now lets test this script is working. We can do this by changing the status code returned if we look for a file that doesn't exist.
location / {
try_files $uri =404;
}
If we send a cURL to a resource Nginx doesn't have then we will get back the 404 page.
curl localhost:6464/woops
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.25.5</center>
</body>
</html>
Now change the status code to 401 in the configuration file and resend the cURL.
location / {
try_files $uri =401;
}
curl localhost:6464/woops
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.25.5</center>
</body>
</html>