Skip to content

Commit

Permalink
chore: Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
fedorov-xyz committed Jun 19, 2024
1 parent 31b4fe1 commit d1619c1
Show file tree
Hide file tree
Showing 16 changed files with 383 additions and 2 deletions.
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/enhanced-nginx.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions .idea/php.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM nginx:1.26.0

LABEL org.opencontainers.image.source="https://github.com/fedorov-xyz/nginx"

RUN apt-get update && apt-get install --no-install-recommends -y \
nano \
curl \
cron \
certbot \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /etc/nginx/

COPY nginx/nginxconfig.io/ ./nginxconfig.io
COPY nginx/nginx.conf .

COPY scripts/entrypoint.sh /
RUN ["chmod", "+x", "/entrypoint.sh"]

COPY scripts/update_cloudflare_ips.sh /usr/local/bin/update_cloudflare_ips.sh
RUN chmod +x /usr/local/bin/update_cloudflare_ips.sh

COPY cron/crontab /etc/cron.d/crontab
RUN chmod 0644 /etc/cron.d/crontab
RUN crontab /etc/cron.d/crontab
RUN touch /var/log/cron.log

# Запуск cron и nginx
CMD ["/entrypoint.sh"]
65 changes: 63 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,63 @@
# nginx
Nginx Docker container with standalone certbot and automatic update of Cloudflare IP address ranges
# Enhanced Nginx

> Documentation in progress
The script to generate the config for Cloudflare is taken from here: https://github.com/ergin/nginx-cloudflare-real-ip

Nginx Docker container with standalone certbot and automatic update of Cloudflare IP address ranges.

## How it works

Instead of agonizing with certbot + nginx + well-known/acme-challenge, let's just let certbot do its job in standalone mode.

When the container is started for the first time, certbot will bring up its server, issue and fail certificates. And then at the very end nginx will start up.

When restarting the container, certbot will check the existing certificate for expiration, and if the expiration is ok, it will just not do anything, nginx will start right away.

## Usage

1. Specify 2 volumes for nginx and letsencrypt
2. Mount your site config to container. You can mount multiple sites

You should use the following lines in your nginx config for the site. `REPLACEMENT_CERT_NAME` will be replaced by the name of the certificate you pass to the container.

```nginx configuration
ssl_certificate /etc/letsencrypt/live/REPLACEMENT_CERT_NAME/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/REPLACEMENT_CERT_NAME/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/REPLACEMENT_CERT_NAME/chain.pem;
```

If you want to serve multiple domains within a container, list their domains in the `SITE_DOMAINS` environment variable. A common certificate will be issued for them.

Example configuration for Docker Compose:

```yaml filename="docker-compose.yml"
volumes:
nginx_data:
letsencrypt_data:

services:
nginx:
image: fedorov-xyz/enhanced-nginx:latest
ports:
- 80:80
- 443:443
volumes:
- nginx_data:/etc/nginx/data
- letsencrypt_data:/etc/letsencrypt
- ./example.com.conf:/sites/example.com.conf
environment:
- SITE_DOMAINS=example.com,staging.example.com
- CERT_NAME=example.com
- CERTBOT_EMAIL=your@email.com
- CERTBOT_TEST_CERT=true
```
## Environment variables list
| Variable | Requirded | Description |
|----------------------|-----------|--------------------------------------------------------------|
| `SITE_DOMAINS` | yes | Сomma-separated list of domains for Let's Encrypt certificate |
| `CERT_NAME` | yes | Certificate name. |
| `CERTBOT_EMAIL` | yes | Email for Let's Encrypt. |
| `CERTBOT_TEST_CERT` | | Pass `true` for test certificates. |
3 changes: 3 additions & 0 deletions cron/crontab
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Auto sync ip addresses of Cloudflare and reload nginx
# Every day at 8:30
30 8 * * * /usr/local/bin/update_cloudflare_ips.sh restart_nginx >/dev/null 2>&1
88 changes: 88 additions & 0 deletions nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Generated by nginxconfig.io
# See nginxconfig.txt for the configuration share link

user nginx;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 65535;

# Load modules
include /etc/nginx/modules-enabled/*.conf;

events {
multi_accept on;
worker_connections 65535;
}

http {
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
log_not_found off;
types_hash_max_size 2048;
types_hash_bucket_size 64;
client_max_body_size 16M;

# MIME
include mime.types;
default_type application/octet-stream;

# Log Format
include nginxconfig.io/log_format.conf;

# Logging
access_log off;
error_log /var/log/nginx/error.log warn;

# SSL
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

# Diffie-Hellman parameter for DHE ciphersuites
ssl_dhparam /etc/nginx/data/dhparam.pem;

# Mozilla Intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
resolver_timeout 2s;

# Connection header for WebSocket reverse proxy
map $http_upgrade $connection_upgrade {
default upgrade;
"" close;
}

map $remote_addr $proxy_forwarded_elem {

# IPv4 addresses can be sent as-is
~^[0-9.]+$ "for=$remote_addr";

# IPv6 addresses need to be bracketed and quoted
~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\"";

# Unix domain socket names cannot be represented in RFC 7239 syntax
default "for=unknown";
}

map $http_forwarded $proxy_add_forwarded {

# If the incoming Forwarded header is syntactically valid, append to it
"~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem";

# Otherwise, replace it
default "$proxy_forwarded_elem";
}

# Load configs and sites
# include /etc/nginx/conf.d/*.conf;
include /etc/nginx/data/cloudflare.conf;
include /etc/nginx/sites.d/*.conf;
}
9 changes: 9 additions & 0 deletions nginx/nginxconfig.io/general.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# favicon.ico
location = /favicon.ico {
log_not_found off;
}

# robots.txt
location = /robots.txt {
log_not_found off;
}
6 changes: 6 additions & 0 deletions nginx/nginxconfig.io/gzip.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
1 change: 1 addition & 0 deletions nginx/nginxconfig.io/log_format.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
log_format cloudflare '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $http_cf_ray $http_cf_connecting_ip $http_cf_ipcountry';
20 changes: 20 additions & 0 deletions nginx/nginxconfig.io/proxy.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;

# Proxy SSL
proxy_ssl_server_name on;

# Proxy headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Forwarded $proxy_add_forwarded;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

# Proxy timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
12 changes: 12 additions & 0 deletions nginx/nginxconfig.io/security.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# security headers
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: ws: wss: data: blob: 'unsafe-inline'; frame-ancestors 'self';" always;
add_header Permissions-Policy "interest-cohort=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# . files
location ~ /\.(?!well-known) {
deny all;
}
75 changes: 75 additions & 0 deletions scripts/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/bash

# Static
DOCKER_DATA=/etc/nginx/data
CLOUDFLARE_FILE_PATH=/etc/nginx/data/cloudflare.conf
DHPARAMS_FILE_PATH=$DOCKER_DATA/dhparams.pem

# External
: "${SITE_DOMAINS:?Must provide SITE_DOMAINS in environment}"
: "${CERT_NAME:?Must provide CERT_NAME in environment}"
: "${CERTBOT_EMAIL:?Must provide CERTBOT_EMAIL in environment}"

mkdir -p $DOCKER_DATA;

echo "Init nginx container"

# Generate dhparams.pem
if [ ! -f $DHPARAMS_FILE_PATH ]; then
echo "Generate dhparams.pem"
openssl dhparam -out $DHPARAMS_FILE_PATH 2048
chmod 600 $DHPARAMS_FILE_PATH
fi

# Generate Cloudflare IP ranges
if [ ! -f $CLOUDFLARE_FILE_PATH ]; then
echo "Generate Cloudflare IP ranges config"
/usr/local/bin/update_cloudflare_ips.sh
fi

# Applying replacements for site configs

cp -r /sites/* /etc/nginx/sites.d/

declare -a replacements=(
"CERT_NAME"
)

for env in "${replacements[@]}"
do
if [ -n "${!env}" ]; then
replacement=$(printf "%s" "${!env}" | sed 's/[,\/&]/\\&/g') # Escape special characters in the replacement value
for file in /etc/nginx/sites.d/*; do
sed -i "s,REPLACEMENT_$env,$replacement,g" "$file" || exit
done
echo " $env is updated in sites.d"
fi
done

# Check cert exist
if [ ! -f /etc/letsencrypt/live/"$CERT_NAME"/fullchain.pem ]; then
echo "Certificate $CERT_NAME do not exists, will generate it now"
fi

echo "Running certbot"

certbot_args=(
certonly
--standalone
--renew-by-default
--non-interactive
--agree-tos
--cert-name "$CERT_NAME"
--email "$CERTBOT_EMAIL"
-d "$SITE_DOMAINS"
)

if [ "$CERTBOT_TEST_CERT" = "true" ]; then
echo "Using staging (test) certificate"
certbot_args+=(--test-cert)
fi

certbot "${certbot_args[@]}" || exit

echo "Start nginx"
nginx -g "daemon off;"
Loading

0 comments on commit d1619c1

Please sign in to comment.