# Bunkerized-Nginx | Nginx Docker Image Secure By Default

**URL:** <https://onehack.st/t/bunkerized-nginx-nginx-docker-image-secure-by-default/190996>\
**Category:** Tools & Scripts\
**Tags:** tools, freebies, networking\
**Created:** [June 23, 2021, 10:41pm UTC](https://onehack.st/t/bunkerized-nginx-nginx-docker-image-secure-by-default/190996 "2021-06-23T22:41:14Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![PlayBoy83](https://onehack.st/user_avatar/onehack.st/playboy83/32/164314_2.png) [@PlayBoy83](https://onehack.st/u/PlayBoy83)\
**Post date:** [June 23, 2021, 10:41pm UTC](https://onehack.st/t/bunkerized-nginx-nginx-docker-image-secure-by-default/190996/1 "2021-06-23T22:41:14Z")

</div>

![image](https://onehack.st/uploads/default/original/3X/9/d/9def255b8fbf1771f97bd1096768cc52e2aef16e.png)

Documentation | Examples | Blog posts | Community chat | Follow us

nginx Docker image secure by default.

Avoid the hassle of following security best practices “by hand” each time you need a web server or reverse proxy. Bunkerized-nginx provides generic security configs, settings and tools so you don’t need to do it yourself.

Non-exhaustive list of features :

HTTPS support with transparent Let’s Encrypt automation  
State-of-the-art web security : HTTP security headers, prevent leaks, TLS hardening, …  
Integrated ModSecurity WAF with the OWASP Core Rule Set  
Automatic ban of strange behaviors  
Antibot challenge through cookie, javascript, captcha or recaptcha v3  
Block TOR, proxies, bad user-agents, countries, …  
Block known bad IP with DNSBL and CrowdSec  
Prevent bruteforce attacks with rate limiting  
Plugins system for external security checks (e.g. : ClamAV)  
Easy to configure with environment variables or web UI  
Automatic configuration with container labels  
Docker Swarm support  
Fooling automated tools/scanners :

You can find a live demo at [https://demo-nginx.bunkerity.com](https://demo-nginx.bunkerity.com), feel free to do some security tests.

Table of contents  
Click to show  
Quickstart guide  
Run HTTP server with default settings  
docker run -p 80:8080 -v /path/to/web/files:/www:ro bunkerity/bunkerized-nginx  
Web files are stored in the /www directory, the container will serve files from there. Please note that bunkerized-nginx doesn’t run as root but as an unprivileged user with UID/GID 101 therefore you should set the rights of /path/to/web/files accordingly.

In combination with PHP  
docker network create mynet  
docker run --network mynet   
-p 80:8080   
-v /path/to/web/files:/www:ro   
-e REMOTE\_PHP=myphp   
-e REMOTE\_PHP\_PATH=/app   
bunkerity/bunkerized-nginx  
docker run --network mynet   
–name myphp   
-v /path/to/web/files:/app   
php:fpm  
The REMOTE\_PHP environment variable lets you define the address of a remote PHP-FPM instance that will execute the .php files. REMOTE\_PHP\_PATH must be set to the directory where the PHP container will find the files.

Run HTTPS server with automated Let’s Encrypt  
docker run -p 80:8080   
-p 443:8443   
-v /path/to/web/files:/www:ro   
-v /where/to/save/certificates:/etc/letsencrypt   
-e SERVER\_NAME=[www.yourdomain.com](http://www.yourdomain.com)   
-e AUTO\_LETS\_ENCRYPT=yes   
-e REDIRECT\_HTTP\_TO\_HTTPS=yes   
bunkerity/bunkerized-nginx  
Certificates are stored in the /etc/letsencrypt directory, you should save it on your local drive. Please note that bunkerized-nginx doesn’t run as root but as an unprivileged user with UID/GID 101 therefore you should set the rights of /where/to/save/certificates accordingly.

If you don’t want your webserver to listen on HTTP add the environment variable LISTEN\_HTTP with a no value (e.g. HTTPS only). But Let’s Encrypt needs the port 80 to be opened so redirecting the port is mandatory.

Here you have three environment variables :

SERVER\_NAME : define the FQDN of your webserver, this is mandatory for Let’s Encrypt ([www.yourdomain.com](http://www.yourdomain.com) should point to your IP address)  
AUTO\_LETS\_ENCRYPT : enable automatic Let’s Encrypt creation and renewal of certificates  
REDIRECT\_HTTP\_TO\_HTTPS : enable HTTP to HTTPS redirection  
As a reverse proxy  
docker run -p 80:8080   
-e USE\_REVERSE\_PROXY=yes   
-e REVERSE\_PROXY\_URL=/   
-e REVERSE\_PROXY\_HOST=[http://myserver:8080](http://myserver:8080)   
bunkerity/bunkerized-nginx  
This is a simple reverse proxy to a unique application. If you have more than one application you can add more REVERSE\_PROXY\_URL/REVERSE\_PROXY\_HOST by appending a suffix number like this :

docker run -p 80:8080   
-e USE\_REVERSE\_PROXY=yes   
-e REVERSE\_PROXY\_URL\_1=/app1/   
-e REVERSE\_PROXY\_HOST\_1=[http://myapp1:3000/](http://myapp1:3000/)   
-e REVERSE\_PROXY\_URL\_2=/app2/   
-e REVERSE\_PROXY\_HOST\_2=[http://myapp2:3000/](http://myapp2:3000/)   
bunkerity/bunkerized-nginx  
Behind a reverse proxy  
docker run -p 80:8080   
-v /path/to/web/files:/www   
-e PROXY\_REAL\_IP=yes   
bunkerity/bunkerized-nginx  
The PROXY\_REAL\_IP environment variable, when set to yes, activates the ngx\_http\_realip\_module to get the real client IP from the reverse proxy.

See this section if you need to tweak some values (trusted ip/network, header, …).

Multisite  
By default, bunkerized-nginx will only create one server block. When setting the MULTISITE environment variable to yes, one server block will be created for each host defined in the SERVER\_NAME environment variable.  
You can set/override values for a specific server by prefixing the environment variable with one of the server name previously defined.

docker run -p 80:8080   
-p 443:8443   
-v /where/to/save/certificates:/etc/letsencrypt   
-e SERVER\_NAME=[app1.domain.com](http://app1.domain.com) [app2.domain.com](http://app2.domain.com)   
-e MULTISITE=yes   
-e AUTO\_LETS\_ENCRYPT=yes   
-e REDIRECT\_HTTP\_TO\_HTTPS=yes   
-e USE\_REVERSE\_PROXY=yes   
-e app1.domain.com\_REVERSE\_PROXY\_URL=/   
-e app1.domain.com\_REVERSE\_PROXY\_HOST=[http://myapp1:8000](http://myapp1:8000)   
-e app2.domain.com\_REVERSE\_PROXY\_URL=/   
-e app2.domain.com\_REVERSE\_PROXY\_HOST=[http://myapp2:8000](http://myapp2:8000)   
bunkerity/bunkerized-nginx  
The USE\_REVERSE\_PROXY is a global variable that will be applied to each server block. Whereas the app1.domain.com\_\* and app2.domain.com\_\* will only be applied to the [app1.domain.com](http://app1.domain.com) and [app2.domain.com](http://app2.domain.com) server block respectively.

When serving files, the web root directory should contains subdirectories named as the servers defined in the SERVER\_NAME environment variable. Here is an example :

docker run -p 80:8080   
-p 443:8443   
-v /where/to/save/certificates:/etc/letsencrypt   
-v /where/are/web/files:/www:ro   
-e SERVER\_NAME=[app1.domain.com](http://app1.domain.com) [app2.domain.com](http://app2.domain.com)   
-e MULTISITE=yes   
-e AUTO\_LETS\_ENCRYPT=yes   
-e REDIRECT\_HTTP\_TO\_HTTPS=yes   
-e app1.domain.com\_REMOTE\_PHP=php1   
-e app1.domain.com\_REMOTE\_PHP\_PATH=/app   
-e app2.domain.com\_REMOTE\_PHP=php2   
-e app2.domain.com\_REMOTE\_PHP\_PATH=/app   
bunkerity/bunkerized-nginx  
The /where/are/web/files directory should have a structure like this :

/where/are/web/files  
├── [app1.domain.com](http://app1.domain.com)  
│ └── index.php  
│ └── …  
└── [app2.domain.com](http://app2.domain.com)  
└── index.php  
└── …  
Automatic configuration  
The downside of using environment variables is that you need to recreate a new container each time you want to add or remove a web service. An alternative is to use the bunkerized-nginx-autoconf image which listens for Docker events and “automagically” generates the configuration.

First we need a volume that will store the configurations :

docker volume create nginx\_conf  
Then we run bunkerized-nginx with the bunkerized-nginx.AUTOCONF label, mount the created volume at /etc/nginx and set some default configurations for our services (e.g. : automatic Let’s Encrypt and HTTP to HTTPS redirect) :

docker network create mynet

docker run -p 80:8080   
-p 443:8443   
–network mynet   
-v /where/to/save/certificates:/etc/letsencrypt   
-v /where/are/web/files:/www:ro   
-v nginx\_conf:/etc/nginx   
-e SERVER\_NAME=   
-e MULTISITE=yes   
-e AUTO\_LETS\_ENCRYPT=yes   
-e REDIRECT\_HTTP\_TO\_HTTPS=yes   
-l bunkerized.nginx.AUTOCONF   
bunkerity/bunkerized-nginx  
When setting SERVER\_NAME to nothing bunkerized-nginx won’t create any server block (in case we only want automatic configuration).

Once bunkerized-nginx is created, let’s setup the autoconf container :

docker run -v /var/run/docker.sock:/var/run/docker.sock:ro   
-v nginx\_conf:/etc/nginx   
bunkerity/bunkerized-nginx-autoconf  
We can now create a new container and use labels to dynamically configure bunkerized-nginx. Labels for automatic configuration are the same as environment variables but with the “bunkerized-nginx.” prefix.

Here is a PHP example :

docker run --network mynet   
–name myapp   
-v /where/are/web/files/app.domain.com:/app   
-l bunkerized-nginx.SERVER\_NAME=[app.domain.com](http://app.domain.com)   
-l bunkerized-nginx.REMOTE\_PHP=myapp   
-l bunkerized-nginx.REMOTE\_PHP\_PATH=/app   
php:fpm  
And a reverse proxy example :

docker run --network mynet   
–name anotherapp   
-l bunkerized-nginx.SERVER\_NAME=[app2.domain.com](http://app2.domain.com)   
-l bunkerized-nginx.USE\_REVERSE\_PROXY=yes   
-l bunkerized-nginx.REVERSE\_PROXY\_URL=/   
-l bunkerized-nginx.REVERSE\_PROXY\_HOST=[http://anotherapp](http://anotherapp)   
tutum/hello-world  
Swarm mode  
Automatic configuration through labels is also supported in swarm mode. The bunkerized-nginx-autoconf is used to listen for Swarm events (e.g. service create/rm) and “automagically” edit configurations files and reload nginx.

As a use case we will assume the following :

Some managers are also workers (they will only run the autoconf container for obvious security reasons)  
The bunkerized-nginx service will be deployed on all workers (global mode) so clients can connect to each of them (e.g. load balancing, CDN, edge proxy, …)  
There is a shared folder mounted on managers and workers (e.g. NFS, GlusterFS, CephFS, …)  
Let’s start by creating the network to allow communications between our services :

docker network create -d overlay mynet  
We can now create the autoconf service that will listen to swarm events :

docker service create --name autoconf   
–network mynet   
–mount type=bind,source=/var/run/docker.sock,destination=/var/run/docker.sock,ro   
–mount type=bind,source=/shared/confs,destination=/etc/nginx   
–mount type=bind,source=/shared/letsencrypt,destination=/etc/letsencrypt   
–mount type=bind,source=/shared/acme-challenge,destination=/acme-challenge   
-e SWARM\_MODE=yes   
-e API\_URI=/ChangeMeToSomethingHardToGuess   
–replicas 1   
–constraint node.role==manager   
bunkerity/bunkerized-nginx-autoconf  
You need to change API\_URI to something hard to guess since there is no other security mechanism to protect the API at the moment.

When autoconf is created, it’s time for the bunkerized-nginx service to be up :

docker service create --name nginx   
–network mynet   
-p published=80,target=8080,mode=host   
-p published=443,target=8443,mode=host   
–mount type=bind,source=/shared/confs,destination=/etc/nginx   
–mount type=bind,source=/shared/letsencrypt,destination=/etc/letsencrypt,ro   
–mount type=bind,source=/shared/acme-challenge,destination=/acme-challenge,ro   
–mount type=bind,source=/shared/www,destination=/www,ro   
-e SWARM\_MODE=yes   
-e USE\_API=yes   
-e API\_URI=/ChangeMeToSomethingHardToGuess   
-e MULTISITE=yes   
-e SERVER\_NAME=   
-e AUTO\_LETS\_ENCRYPT=yes   
-e REDIRECT\_HTTP\_TO\_HTTPS=yes   
-l bunkerized-nginx.AUTOCONF   
–mode global   
–constraint node.role==worker   
bunkerity/bunkerized-nginx  
The API\_URI value must be the same as the one specified for the autoconf service.

We can now create a new service and use labels to dynamically configure bunkerized-nginx. Labels for automatic configuration are the same as environment variables but with the “bunkerized-nginx.” prefix.

Here is a PHP example :

docker service create --name myapp   
–network mynet   
–mount type=bind,source=/shared/www/app.domain.com,destination=/app   
-l bunkerized-nginx.SERVER\_NAME=[app.domain.com](http://app.domain.com)   
-l bunkerized-nginx.REMOTE\_PHP=myapp   
-l bunkerized-nginx.REMOTE\_PHP\_PATH=/app   
–constraint node.role==worker   
php:fpm  
And a reverse proxy example :

docker service create --name anotherapp   
–network mynet   
-l bunkerized-nginx.SERVER\_NAME=[app2.domain.com](http://app2.domain.com)   
-l bunkerized-nginx.USE\_REVERSE\_PROXY=yes   
-l bunkerized-nginx.REVERSE\_PROXY\_URL=/   
-l bunkerized-nginx.REVERSE\_PROXY\_HOST=[http://anotherapp](http://anotherapp)   
–constraint node.role==worker   
tutum/hello-world  
Web UI  
A dedicated image, bunkerized-nginx-ui, lets you manage bunkerized-nginx instances and services configurations through a web user interface. This feature is still in beta, feel free to open a new issue if you find a bug and/or you have an idea to improve it.

First we need a volume that will store the configurations and a network because bunkerized-nginx will be used as a reverse proxy for the web UI :

docker volume create nginx\_conf  
docker network create mynet  
Let’s create the bunkerized-nginx-ui container that will host the web UI behind bunkerized-nginx :

docker run --network mynet   
–name myui   
-v /var/run/docker.sock:/var/run/docker.sock:ro   
-v nginx\_conf:/etc/nginx   
-e ABSOLUTE\_URI=[https://admin.domain.com/webui/](https://admin.domain.com/webui/)   
bunkerity/bunkerized-nginx-ui  
You will need to edit the ABSOLUTE\_URI environment variable to reflect your actual URI of the web UI.

We can now setup the bunkerized-nginx instance with the bunkerized-nginx.UI label and a reverse proxy configuration for our web UI :

docker network create mynet

docker run -p 80:8080   
-p 443:8443   
–network mynet   
-v nginx\_conf:/etc/nginx   
-v /where/are/web/files:/www:ro   
-v /where/to/save/certificates:/etc/letsencrypt   
-e SERVER\_NAME=[admin.domain.com](http://admin.domain.com)   
-e MULTISITE=yes   
-e AUTO\_LETS\_ENCRYPT=yes   
-e REDIRECT\_HTTP\_TO\_HTTPS=yes   
-e DISABLE\_DEFAULT\_SERVER=yes   
-e admin.domain.com\_USE\_MODSECURITY=no   
-e admin.domain.com\_SERVE\_FILES=no   
-e admin.domain.com\_USE\_AUTH\_BASIC=yes   
-e admin.domain.com\_AUTH\_BASIC\_USER=admin   
-e admin.domain.com\_AUTH\_BASIC\_PASSWORD=password   
-e admin.domain.com\_USE\_REVERSE\_PROXY=yes   
-e admin.domain.com\_REVERSE\_PROXY\_URL=/webui/   
-e admin.domain.com\_REVERSE\_PROXY\_HOST=[http://myui:5000/](http://myui:5000/)   
-l bunkerized-nginx.UI   
bunkerity/bunkerized-nginx  
The AUTH\_BASIC environment variables let you define a login/password that must be provided before accessing to the web UI. At the moment, there is no authentication mechanism integrated into bunkerized-nginx-ui so using auth basic with a strong password coupled with a “hard to guess” URI is strongly recommended.

Web UI should now be accessible from [https://admin.domain.com/webui/](https://admin.domain.com/webui/).

Security tuning  
bunkerized-nginx comes with a set of predefined security settings that you can (and you should) tune to meet your own use case. We recommend you to read the security tuning section of the documentation.

## GitHub:

> **[GitHub - bunkerity/bunkerweb: 🛡️ Open-source and next-generation Web Application...](https://github.com/bunkerity/bunkerweb)**
>
> 🛡️ Open-source and next-generation Web Application Firewall (WAF)
