Skip to content

update to include geoip, zstd and http3 #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 14, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -24,7 +24,11 @@ ENV VAR_PREFIX=/var/run \
CACHE_PREFIX=/var/cache \
CONF_PREFIX=/etc/nginx \
CERTS_PREFIX=/etc/pki/tls \
NGINX_DOCROOT=/usr/share/nginx/html
NGINX_DOCROOT=/usr/share/nginx/html \
GEOIP_PREFIX=/usr/share/geoip

# Set working directory to NGINX_DOCROOT
WORKDIR ${NGINX_DOCROOT}

# Create www-data user and group
RUN if ! getent group www-data >/dev/null; then \
@@ -170,7 +174,7 @@ ENV CONFIG="\
--with-http_v3_module \
--add-module=/tmp/ngx_cache_purge-${NGX_CACHE_PURGE_VERSION} \
--add-module=/tmp/ngx_http_redis-${NGX_REDIS_VERSION} \
--add-module=/tmp/ngx_http_geoip2_module \
--add-dynamic-module=/tmp/ngx_http_geoip2_module \
--add-module=/tmp/redis2-nginx-module-${NGX_REDIS2_VERSION} \
--add-module=/tmp/srcache-nginx-module-${NGX_SRCACHE_VERSION} \
--add-module=/tmp/echo-nginx-module \
@@ -195,6 +199,9 @@ RUN cd /usr/src/nginx-$NGINX_VERSION \
RUN mkdir -p ${CERTS_PREFIX} \
&& openssl dhparam -out ${CERTS_PREFIX}/dhparam.pem.default 4096

# Create GeoIP directory
RUN mkdir -p ${GEOIP_PREFIX}

# Cleanup
RUN apk del .build-deps \
&& rm -rf /tmp/* \
@@ -215,7 +222,8 @@ ENV VAR_PREFIX=/var/run \
CACHE_PREFIX=/var/cache \
CONF_PREFIX=/etc/nginx \
CERTS_PREFIX=/etc/pki/tls \
NGINX_DOCROOT=/usr/share/nginx/html
NGINX_DOCROOT=/usr/share/nginx/html \
GEOIP_PREFIX=/usr/share/geoip

# Create www-data user
RUN if ! getent group www-data >/dev/null; then \
@@ -249,6 +257,7 @@ COPY --from=builder /usr/lib/nginx /usr/lib/nginx
COPY --from=builder /usr/share/nginx /usr/share/nginx
COPY --from=builder /etc/nginx /etc/nginx
COPY --from=builder ${CERTS_PREFIX} ${CERTS_PREFIX}
COPY --from=builder ${GEOIP_PREFIX} ${GEOIP_PREFIX}

# Create necessary directories and symlinks
RUN mkdir -p ${LOG_PREFIX} \
@@ -265,8 +274,12 @@ COPY conf/ /conf
COPY error/ ${NGINX_DOCROOT}/error
COPY docker-entrypoint.sh /docker-entrypoint.sh

# Copy GeoIP databases
COPY geoip/*.mmdb ${GEOIP_PREFIX}/

# Set permissions
RUN chmod +x /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh \
&& chown -R www-data:www-data ${GEOIP_PREFIX}

# Set stop signal
STOPSIGNAL SIGQUIT
60 changes: 2 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
@@ -34,23 +34,14 @@ The first step is to build or pull the image:

### Build
```docker
docker build --build-arg "NGINX_VERSION=1.15.5" -t openbridge/nginx .
docker build --build-arg "NGINX_VERSION=1.27.4" -t openbridge/nginx .
```
Replace `NGINX_VERSION=1.17.0` with your preferred version. You can also simply `pull` the images. See below.
Replace `NGINX_VERSION=1.27.4` with your preferred version. You can also simply `pull` the images. See below.
### Pull
```docker
docker pull openbridge/nginx:latest
```

You can also use a different version of NGINX simply by pulling a build with the NGINX version you want. For example;
```docker
docker pull openbridge/nginx:latest
docker pull openbridge/nginx:1.15.4
docker pull openbridge/nginx:1.15.3
```
To see the available versions vist https://hub.docker.com/r/openbridge/nginx/tags/


## Running

Via Docker compose
@@ -491,53 +482,6 @@ This allows you to connect to `https://localhost/testing/test_info.php` to verif

Noe: Using PHP assumes you have configured a PHP backend to test anything PHP related

# Monitoring
Services in the container are monitored via Monit. One thing to note is that if Monit detects a problem with Nginx it will issue a `STOP` command. This will shutdown your container because the image uses `CMD ["nginx", "-g", "daemon off;"]`. If you are using `--restart unless-stopped` in your docker run command the server will automatically restart.

Here is an example monitoring config:
```nginx
check process nginx with pidfile "/var/run/nginx.pid"
if not exist for 5 cycles then restart
start program = "/usr/bin/env bash -c '/usr/sbin/nginx -g daemon off'" with timeout 60 seconds
stop program = "/usr/bin/env bash -c '/usr/sbin/nginx -s stop'"
every 3 cycles
if cpu > 80% for 10 cycles then exec "/usr/bin/env bash -c '/usr/sbin/nginx -s stop'"

check program wwwdata-permissions with path /usr/bin/env bash -c "check_wwwdata permission"
every 3 cycles
if status != 0 then exec "/usr/bin/env bash -c 'find {{NGINX_DOCROOT}} -type d -exec chmod 755 {} \; && find {{NGINX_DOCROOT}} -type f -exec chmod 644 {} \;'"

check directory cache-permissions with path {{CACHE_PREFIX}}
every 3 cycles
if failed permission 755 then exec "/usr/bin/env bash -c 'find {{CACHE_PREFIX}} -type d -exec chmod 755 {} \;'"

check directory cache-owner with path {{CACHE_PREFIX}}
every 3 cycles
if failed uid www-data then exec "/usr/bin/env bash -c 'find {{CACHE_PREFIX}} -type d -exec chown www-data:www-data {} \; && find {{CACHE_PREFIX}} -type f -exec chown www-data:www-data {} \;'"

check file letsencrypt_certificate with path /etc/letsencrypt/live/{{NGINX_SERVER_NAME}}/fullchain.pem
if changed checksum then exec "/usr/bin/env bash -c '/usr/sbin/nginx -s reload'"

check host {{NGINX_SERVER_NAME}} with address {{NGINX_SERVER_NAME}}
if failed host {{NGINX_SERVER_NAME}} port 80 protocol http
and request "/health-check"
with timeout 25 seconds
for 3 times within 4 cycles
then exec "/usr/bin/env bash -c '/usr/sbin/nginx -s reload'"
if failed host {{NGINX_SERVER_NAME}} port 443 protocol https
request "/health-check"
status = 200
content = "healthy"
with timeout 25 seconds
for 3 times within 4 cycles
then exec "/usr/bin/env bash -c '/usr/sbin/nginx -s reload'"
if failed port 8080 for 3 cycles then exec "/usr/bin/env bash -c '/usr/sbin/nginx -s stop'"

check program cache-size with path /usr/bin/env bash -c "check_folder {{CACHE_PREFIX}} 500"
every 20 cycles
if status != 0 then exec "/usr/bin/env bash -c 'rm -Rf /var/cache/*'"
```
The `check_folder`, `check_host` and `check_wwwdata` scripts provide additional health check utility of make sure that permissions, cache size and host respond correctly. For example, `check_host` will validate that SPA rendering service is properly serving the expected content. This can help detect if there are issues where certain user-agents that can not render SPA are being served the incorrect content. This can wreak havoc with your SEO if a pre-render service is not working as expected. Best to catch it as early as possible so you can mitigate any issues.

# Content Delivery Network
If you want to activate CDN for assets like images, you can set your location to redirect those requests to your CDN:
10 changes: 8 additions & 2 deletions conf/php/nginx/conf.d/brotli.conf
Original file line number Diff line number Diff line change
@@ -5,7 +5,8 @@ brotli_static on;
# Compression settings
brotli_comp_level 5; # Compression level (1-11, higher values provide better compression at the cost of CPU usage)
brotli_min_length 256; # Only compress responses larger than 256 bytes
brotli_buffers 8 8k; # Number and size of buffers used for compression
brotli_buffers 16 8k; # Increased number of buffers for better handling of concurrent requests
brotli_window 512k; # Explicitly set window size (default is 512k)

# Specify MIME types for Brotli compression, grouped by category

@@ -42,10 +43,15 @@ brotli_types

# Text types
text/cache-manifest
text/calendar
text/css
text/html
text/javascript
text/markdown
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy;
text/x-cross-domain-policy
text/xml;
24 changes: 24 additions & 0 deletions conf/php/nginx/conf.d/geo.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# GeoIP2 database settings for HTTP context
geoip2 /usr/share/geoip/GeoLite2-Country.mmdb {
auto_reload 5m;
$geoip2_metadata_country_build metadata build_epoch;
$geoip2_data_country_code default=- country iso_code;
$geoip2_data_country_name default=- country names en;
}

geoip2 /usr/share/geoip/GeoLite2-City.mmdb {
auto_reload 5m;
$geoip2_metadata_city_build metadata build_epoch;
$geoip2_data_city_name default=- city names en;
$geoip2_data_region_name default=- subdivisions 0 names en;
$geoip2_data_region_code default=- subdivisions 0 iso_code;
$geoip2_data_postal_code default=- postal code;
$geoip2_data_latitude default=- location latitude;
$geoip2_data_longitude default=- location longitude;
}

geoip2 /usr/share/geoip/GeoLite2-ASN.mmdb {
auto_reload 5m;
$geoip2_data_asn default=- autonomous_system_number;
$geoip2_data_asn_org default=- autonomous_system_organization;
}
11 changes: 8 additions & 3 deletions conf/php/nginx/conf.d/gzip.conf
Original file line number Diff line number Diff line change
@@ -2,9 +2,9 @@
gzip on;

# Compression settings
gzip_comp_level 7; # Compression level (1-9); 7 is a good balance of speed and compression
gzip_comp_level 6; # Slightly reduced from 7 for better CPU balance
gzip_min_length 256; # Only compress responses larger than 256 bytes
gzip_buffers 8 4k; # Number and size of buffers for compression
gzip_buffers 16 8k; # Increased buffer size for better performance
gzip_http_version 1.1; # Only compress responses for HTTP/1.1 and above

# Specify MIME types for gzip compression, grouped by category
@@ -20,9 +20,11 @@ gzip_types
application/rss+xml
application/vnd.ms-fontobject
application/wasm
application/x-javascript
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
application/octet-stream

# Font types (include these if your fonts are not already compressed)
font/eot
@@ -36,11 +38,13 @@ gzip_types
image/bmp
image/svg+xml
image/webp
image/x-icon

# Text types
text/cache-manifest
text/calendar
text/css
text/html # Added critical text/html MIME type
text/javascript
text/markdown
text/plain
@@ -54,4 +58,5 @@ gzip_types
# Additional gzip settings
gzip_vary on; # Enable Vary header for proxies and clients
gzip_proxied any; # Enable compression for all proxied requests
gzip_disable "MSIE [1-6]\.(?!.*SV1)"; # Disable gzip for old versions of Internet Explorer
gzip_static on; # Serve pre-compressed .gz files if available
gzip_disable "MSIE [1-6]\.(?!.*SV1)"; # Disable gzip for old versions of Internet Explorer
127 changes: 67 additions & 60 deletions conf/php/nginx/conf.d/secure.conf
Original file line number Diff line number Diff line change
@@ -21,75 +21,82 @@ location ^~ /.well-known/acme-challenge/ {
allow all;
}

##############################################
# WordPress Specific Allowances
##############################################

# WordPress REST API - must be defined early to ensure valid API calls are handled correctly
location /wp-json/ {
try_files $uri $uri/ /index.php$is_args$args;
}

# Allow wp-includes assets (CSS, JS, images, etc.)
location ~* ^/wp-includes/.*\.(js|css|png|jpg|jpeg|gif|ico)$ {
allow all;
}

##############################################
# Blocking Sensitive Files & Directories
##############################################

# Block sensitive WordPress configuration files
location ~* ^/(wp-config\.php|wp-config-sample\.php)$ {
return 403;
}

# Block sensitive system files
location ~* ^/(boot\.ini|etc/passwd|self/environ)$ {
return 403;
}

# Block hidden files and repository directories
# Exclude /wp-admin and /wp-json to avoid blocking legitimate admin/API requests
location ~* ^/(\..*|Entries.*|Repository|Root|Tag|Template|wp-content/uploads/nginx-helper/)$ {
if ($request_uri !~* ^/(wp-admin|wp-json)) {
# # # WordPress Admin Area Access Control
location ~ ^/wp-admin($|/) {
# Check if the user is allowed based on geo location
if ($wp_admin_allowed = 0) {
return 403;
}
}

# Block PHP execution in uploads and similar directories
location ~* ^/(wp-content/uploads|wp-content/uploads/sucuri|wp-content/updraft)/.*\.php$ {
return 403;
}

##############################################
# Blocking Malicious Patterns
##############################################
# Regular WordPress handling
try_files $uri $uri/ /index.php$is_args$args;

# Block potentially malicious patterns (e.g. eval(), phpinfo, etc.)
# Exempt /wp-json requests to ensure valid REST API calls aren’t blocked
location ~* (&pws=0|_vti_|\(null\)|(eval\\(|self/environ|mobiquo|phpinfo|shell\.php|sqlpatch|webshell\.php)) {
if ($request_uri !~* ^/wp-json/) {
return 403;
# PHP processing
location ~ \.php$ {
fastcgi_pass php-fpm;
include /etc/nginx/fastcgi.d/fastcgi.conf;
}
}

# Block SQL injection attempts
# Exempt /wp-json requests so legitimate API calls aren’t affected
location ~* (drop[^-]|insert|md5|select|union|=%27|/'/?) {
if ($request_uri !~* ^/wp-json/) {
# # Also protect wp-login.php
location = /wp-login.php {
# Check if the user is allowed based on geo location
if ($wp_admin_allowed = 0) {
return 403;
}
}

##############################################
# Blocking Dangerous File Extensions
##############################################

# Block access to files with dangerous extensions
# Exclude /wp-admin and /wp-json to avoid interfering with valid backend/API functionality
location ~* \.(bak|conf|dist|fla|in[ci]|log|psd|sh|sql|sw[op]|engine|inc|info|install|make|module|profile|test|po|.*sql|tpl\.php|php_|pl|cgi|py|lua|asp|jsp)$ {
if ($request_uri !~* ^/(wp-admin|wp-json)) {
return 403;
}
# PHP processing
fastcgi_pass php-fpm;
include /etc/nginx/fastcgi.d/fastcgi.conf;
}

location = /geoip-test {
add_header Content-Type text/plain;
return 200 "Remote Address: $remote_addr\n
IP Detection:\n
- X-Forwarded-For: $http_x_forwarded_for\n
- X-Real-IP: $http_x_real_ip\n
\n
GeoIP Information:\n
- Country Code: $geoip2_data_country_code\n
- Country Name: $geoip2_data_country_name\n
- City Name: $geoip2_data_city_name\n
- Region Name: $geoip2_data_region_name\n
- Region Code: $geoip2_data_region_code\n
- Postal Code: $geoip2_data_postal_code\n
- Latitude: $geoip2_data_latitude\n
- Longitude: $geoip2_data_longitude\n
- ASN: $geoip2_data_asn\n
- ASN Org: $geoip2_data_asn_org\n
\n
GeoIP Database Info:\n
- Country DB Build Epoch: $geoip2_metadata_country_build\n
- City DB Build Epoch: $geoip2_metadata_city_build\n";
}

# Also add a JSON version for programmatic testing
location = /geoip-test.json {
add_header Content-Type application/json;
return 200 '{
"remote_addr": "$remote_addr",
"x_forwarded_for": "$http_x_forwarded_for",
"x_real_ip": "$http_x_real_ip",
"geoip2": {
"country_code": "$geoip2_data_country_code",
"country_name": "$geoip2_data_country_name",
"city_name": "$geoip2_data_city_name",
"region_name": "$geoip2_data_region_name",
"region_code": "$geoip2_data_region_code",
"postal_code": "$geoip2_data_postal_code",
"latitude": "$geoip2_data_latitude",
"longitude": "$geoip2_data_longitude",
"asn": "$geoip2_data_asn",
"asn_org": "$geoip2_data_asn_org"
},
"database_info": {
"country_build": "$geoip2_metadata_country_build",
"city_build": "$geoip2_metadata_city_build"
}
}';
}
Loading
Oops, something went wrong.