{"id":48754653,"url":"https://github.com/alaub81/nginxproxy","last_synced_at":"2026-04-13T00:38:46.907Z","repository":{"id":324776075,"uuid":"1096325523","full_name":"alaub81/nginxproxy","owner":"alaub81","description":"nginx proxy inlsusive imgproxy, goaccess statistics and certbot for SSL - docker compose setup and example config files","archived":false,"fork":false,"pushed_at":"2026-02-18T07:23:42.000Z","size":74,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-18T12:18:54.994Z","etag":null,"topics":["certbot","docker","docker-compose","goaccess","imgproxy","letsencrypt","logrotate","nginx","nginxproxy","ssl-certificates"],"latest_commit_sha":null,"homepage":"https://lhlab.wiki/wiki/Nginx_Reverse%E2%80%91Proxy_Stack_-_TLS,_Caching,_Statistik_und_AVIF/WebP_on_the_fly","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alaub81.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/funding.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"buy_me_a_coffee":"alaub81"}},"created_at":"2025-11-14T09:05:05.000Z","updated_at":"2026-02-18T07:23:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/alaub81/nginxproxy","commit_stats":null,"previous_names":["alaub81/nginxproxy"],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/alaub81/nginxproxy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alaub81%2Fnginxproxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alaub81%2Fnginxproxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alaub81%2Fnginxproxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alaub81%2Fnginxproxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alaub81","download_url":"https://codeload.github.com/alaub81/nginxproxy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alaub81%2Fnginxproxy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31735537,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-12T22:19:12.206Z","status":"ssl_error","status_checked_at":"2026-04-12T22:18:33.088Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["certbot","docker","docker-compose","goaccess","imgproxy","letsencrypt","logrotate","nginx","nginxproxy","ssl-certificates"],"created_at":"2026-04-13T00:38:44.336Z","updated_at":"2026-04-13T00:38:46.898Z","avatar_url":"https://github.com/alaub81.png","language":"Shell","funding_links":["https://buymeacoffee.com/alaub81"],"categories":[],"sub_categories":[],"readme":"# Nginx Reverse‑Proxy Stack (Nginx + Certbot + imgproxy + GoAccess)\n\nA production‑ready Docker Compose stack that fronts your web apps (e.g., MediaWiki)\nwith **Nginx**, handles **TLS automation with Certbot**, serves **next‑gen images via imgproxy**\n(AVIF/WebP negotiation with caching \u0026 failover), and provides **real‑time traffic analytics with GoAccess**\n(WebSocket proxied, behind Basic Auth). It also includes sane **log rotation** and **proxy cache** settings.\n\n\u003e The stack is designed to run on a single host with Docker, expose multiple virtual hosts,\n\u003e and keep configuration tidy via reusable “includes”.\n\n---\n\n## Features\n\n- **TLS automation (Let’s Encrypt / Certbot)**\n  - Webroot HTTP‑01 challenge via a dedicated `/.well-known/acme-challenge/` location.\n  - One‑shot issuance script from a `domains.list` file (one certificate per line).\n  - Safe renewals (no need to stop Nginx) and dry‑run support.\n  - Ready for OCSP stapling (subject to CA certificate OCSP URL availability).\n\n- **Hardened reverse proxy**\n  - HTTP/2, SNI, and strict security headers (HSTS, X‑Content‑Type‑Options, etc.).\n  - Clean separation via `includes/` for security headers, caching, certbot, and upstream maps.\n  - ENV‑driven backend mapping so TEST/PROD switches are minimal.\n\n- **Smart image delivery with imgproxy**\n  - Content negotiation: AVIF → WebP → PNG/JPEG fallback based on `Accept`.\n  - Long‑lived, immutable caching with variant‑aware cache keys.\n  - **Failover:** if imgproxy is unavailable, Nginx falls back to origin thumbnails.\n  - SVG passthrough (no accidental raster conversion).\n\n- **Real‑time analytics with GoAccess**\n  - Uses a single **virtual‑host aware** access log (per‑vhost analytics).\n  - Real‑time HTML dashboard via WebSockets proxied at `/ws`.\n  - **Basic Auth** on the stats vhost; WS endpoint whitelisted with an **Origin** gate.\n  - Custom **bot list** and referrer filters to keep “Unique Visitors” sane.\n  - Persistent DB so historical stats survive container restarts.\n\n- **Logging \u0026 rotation**\n  - Unified `access_all.log` + error logs per vhost (optional).\n  - **logrotate sidecar** with `copytruncate` for zero‑downtime rotation.\n  - Size/time‑based retention with compression.\n\n---\n\n## Prerequisites\n\n- Docker \u0026 Docker Compose\n- DNS A/AAAA records pointing to your host\n- Ports **80** and **443** reachable from the internet\n\n---\n\n## Quickstart\n\n1. **Clone \u0026 enter the repository**\n\n   ```bash\n   cd /opt\n   git clone \u003cthis-repo-url\u003e nginxproxy\n   cd nginxproxy\n   ```\n\n2. **Copy example files (adjust parameters as needed)**\n\n   ```bash\n   # Environment\n   cp .env.example .env\n\n   # Nginx vhosts \u0026 includes\n   cp -r data/nginx/conf.d/global.conf.example data/nginx/conf.d/global.conf\n   cp -r data/nginx/conf.d/stats.example.com.conf.example data/nginx/conf.d/\u003cstats.example.com.conf\u003e\n   cp -r data/nginx/conf.d/www.example.com.conf.example data/nginx/conf.d/\u003cwww.example.com.conf\u003e\n   cp -r data/nginx/conf.d/includes/certbot.conf.example data/nginx/conf.d/includes/certbot.conf\n   cp -r data/nginx/conf.d/includes/security-headers.conf.example data/nginx/conf.d/includes/security-headers.conf\n   cp -r data/nginx/conf.d/includes/site-defaults.conf.example data/nginx/conf.d/includes/site-defaults.conf\n   cp -r data/nginx/conf.d/includes/ssl.conf.example data/nginx/conf.d/includes/ssl.conf\n\n   # Certbot webroot \u0026 config (persisted)\n   mkdir -p data/letsencrypt/{conf,webroot,lib,logs}\n\n   # GoAccess config \u0026 dashboards\n   cp data/goaccess/goaccess.conf.example data/goaccess/conf/goaccess.conf\n   cp data/goaccess/browsers.list.example data/goaccess/conf/browsers.list\n\n   # Logrotate sidecar\n   cp data/logrotate/nginx-acccess.example data/logrotate/conf/nginx-access\n   ```\n\n3. **Define certificates to issue**\n\n   - Edit `issue-from-list.sh` variables on top\n   - Edit `domains.list` — *one certificate per line*, first domain is the cert name:\n\n     ```text\n     example.com www.example.com\n     stats.example.com\n     ```\n\n4. **Bring the stack up**\n\n   ```bash\n   docker compose up -d\n   ```\n\n5. **Issue certificates (staging/dry‑run first)**\n\n   ```bash\n   ./issue-from-list.sh domains.list   # uses the running certbot container\n   ```\n\n   - The script supports `--staging` / `--dry-run` toggles internally; switch off for production issuance.\n\n6. **Visit your sites**\n\n   - Your app vhosts (e.g., `https://www.example.com`)\n   - Real‑time stats at `https://stats.example.com` (behind Basic Auth)\n\n---\n\n## Folder layout (typical)\n\n```txt\n.\n├─ docker-compose.yml\n├─ .env\n├─ geoipupdate.env\n├─ data/\n│  ├─ nginx/\n│  │  └─ conf.d/\n│  │     ├─ global.conf\n│  │     ├─ \u003cvhosts\u003e.conf\n│  │     └─ includes/\n│  │        ├─ security.conf\n│  │        ├─ certbot.conf\n│  │        ├─ cache.conf\n│  │        └─ upstreams.map.conf\n│  ├─ letsencrypt/\n│  │  ├─ conf/            # certs (mounted read-only into nginx)\n│  │  └─ webroot/         # HTTP-01 challenge files\n│  └─ goaccess/\n│     ├─ goaccess.conf\n│     └─ browsers.list\n├─ issue-from-list.sh\n├─ domains.list\n├─ nginx_imgproxy_testing.sh\n└─ goaccess-referrer-ignore.sh\n\n```\n\n---\n\n## Configuration highlights\n\n### 1) Certbot challenge (works for issuance **and** renewals)\n\n```nginx\n# includes/certbot.conf\nlocation ^~ /.well-known/acme-challenge/ {\n    root /srv/certbot/www;\n    default_type \"text/plain\";\n    add_header Cache-Control \"no-store\";\n    try_files $uri =404;\n    auth_basic off;\n    allow all;\n}\n```\n\n### 2) Image negotiation \u0026 caching (imgproxy in front, fallback to origin)\n\n```nginx\n# Map Accept -\u003e target format\nmap $http_accept $imgfmt {\n    \"~*image/avif\"  \"avif\";\n    \"~*image/webp\"  \"webp\";\n    default         \"png\";\n}\n\n# Cache key must vary by format!\nproxy_cache_path /var/cache/nginx/img levels=1:2 keys_zone=img_cache:50m inactive=30d max_size=5g;\n\nlocation ~* ^/images/(?:thumb/)?(.+\\.(?:jpe?g|png|gif|webp|avif))$ {\n    set $src \"http://mediawiki$uri$is_args$args\";   # or env-driven upstream\n\n    # variant-aware cache\n    proxy_cache        img_cache;\n    proxy_cache_key    \"$scheme$proxy_host$uri|$imgfmt\";\n    add_header X-Cache $upstream_cache_status always;\n\n    # pass to imgproxy\n    proxy_pass http://imgproxy:8989/insecure/plain/$src@$imgfmt;\n\n    # serve stale on trouble \u0026 fallback to origin\n    proxy_cache_use_stale error timeout invalid_header http_500 http_502 http_503 http_504 updating;\n    proxy_next_upstream  error timeout http_500 http_502 http_503 http_504 non_idempotent;\n    error_page 502 503 504 = @origin_fallback;\n\n    proxy_hide_header Vary;\n    add_header Vary Accept always;\n    expires 30d;\n    add_header Cache-Control \"public, max-age=2592000, immutable\";\n}\n\nlocation @origin_fallback {\n    proxy_pass http://mediawiki;\n    expires 30d;\n    add_header Cache-Control \"public, max-age=2592000, immutable\";\n}\n```\n\n### 3) Stats vhost (GoAccess) with Basic Auth \u0026 WebSocket proxy\n\n```nginx\n# Protect everything by default\nauth_basic \"Restricted\";\nauth_basic_user_file /etc/nginx/conf.d/.htpasswd.pwd;\n\n# Real-time WS under /ws (no Basic Auth, but origin-gated)\nlocation /ws {\n    auth_basic off;\n    if ($http_origin !~* \"^https://stats\\.example\\.com$\") { return 403; }\n\n    proxy_pass http://goaccess:7890;\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"Upgrade\";\n    proxy_set_header Host $host;\n}\n```\n\n### 4) Access log with virtual host + UA\n\n```nginx\n# http {} block\nlog_format vcombined '$host $remote_addr - $remote_user '\n                     '[$time_local] \"$request\" $status $body_bytes_sent '\n                     '\"$http_referer\" \"$http_user_agent\"';\naccess_log /var/log/nginx/access_all.log vcombined;\n```\n\n---\n\n## Environment Configuration (`.env`) — Reference\n\nThis chapter documents every variable in `nginxproxy/.env.example` and when/how to change it. Copy the file to `.env` and adjust the values for your setup.\n\n---\n\n### Project basics\n\n| Variable | Example | Purpose | Notes |\n|---|---|---|---|\n| `PROJECT_NAME` | `nginxproxy` | Docker Compose project name | Used as container/network prefix. Keep it short and stable. |\n\n---\n\n### GoAccess (real-time analytics)\n\n| Variable | Example | Purpose | Notes |\n|---|---|---|---|\n| `GOACCESS_PORT` | `127.0.0.1:7890` | Bind address for GoAccess WebSocket server | Keep it on `127.0.0.1` and proxy it via NGINX; don’t expose publicly. |\n| `GA_SSL_KEY` | `/etc/letsencrypt/live/stats.example.com/privkey.pem` | TLS key used by GoAccess itself | Use certs that match your stats domain. |\n| `GA_SSL_CERT` | `/etc/letsencrypt/live/stats.example.com/fullchain.pem` | TLS full chain for GoAccess | Same certificate pair as your stats vhost. |\n| `GA_WS_URL` | `wss://stats.example.com/ws` | External WS URL GoAccess advertises to the HTML page | Must be `wss://…` when served behind HTTPS. |\n| `GA_ORIGIN` | `https://stats.example.com` | Allowed browser origin for WebSocket connections | Must match the public URL serving the dashboard. |\n\n**Tips**\n\n- If you terminate TLS at NGINX only, you can still run GoAccess with `GOACCESS_PORT=127.0.0.1:7890` and let the vhost proxy `/ws` to it.  \n- Ensure your NGINX vhost forwards `Upgrade` and `Connection` headers for WebSocket.\n\n---\n\n### imgproxy (on-the-fly image optimization)\n\n| Variable | Example | Purpose | Notes |\n|---|---|---|---|\n| `IMGPROXY_BIND` | `127.0.0.1:8989` | Bind address for imgproxy | Keep it on loopback; vhost proxies requests. |\n| `IMGPROXY_ALLOWED_SOURCES` | `http://mediawiki:8091,http://127.0.0.1:8091` | Comma-separated list of allowed source origins | Add all backends that serve your original images (MediaWiki, test env, etc.). |\n| `IMGPROXY_ALLOW_LOOPBACK_SOURCE_ADDRESSES` | `true` | Allow `127.0.0.1`/`localhost` as image sources | Useful in container-to-container setups. |\n| `IMGPROXY_ENFORCE_WEBP` | `false` | Force WebP output for all clients | Usually keep `false` and negotiate by `Accept`. |\n| `IMGPROXY_PREFER_WEBP` | `false` | Prefer WebP when client supports it | You can negotiate in NGINX; leaving `false` is fine. |\n| `IMGPROXY_QUALITY` | `75` | Default quality for non-WebP formats | 70–80 is a good balance. |\n| `IMGPROXY_WEBP_QUALITY` | `75` | Quality for WebP output | Can often be a bit lower for similar visual quality. |\n| `IMGPROXY_STRIP_METADATA` | `true` | Remove EXIF/ICC/etc. | Saves bytes and avoids leaking camera/location data. |\n| `IMGPROXY_MAX_SRC_RESOLUTION` | `50` | Max source megapixels (width × height ÷ 1e6) | Protects against huge inputs; set `0` to disable. |\n| `IMGPROXY_DOWNLOAD_TIMEOUT` | `5` | Max seconds to fetch source image | Tune if backends are slow. |\n| `IMGPROXY_READ_REQUEST_TIMEOUT` | `5` | Max seconds to read request | Safety limit for slow clients. |\n\n**Security recommendations**\n\n- Keep imgproxy bound to `127.0.0.1` and only reachable via NGINX.\n\n---\n\n### Timezone\n\n| Variable | Example | Purpose |\n|---|---|---|\n| `TZ` | `Europe/Berlin` | Container timezone for logs and time-based tasks. |\n\n---\n\n## goaccess\n\n### `goaccess-referrer-ignore.sh`\n\n## generate Basic Auth Password file (.htpasswd)\n\nwe will use the **apache http docker image** and generate the `.htpasswd` file with that container,\nbecause nginx does not come with a `htpasswd` binary.\n\n```bash\n# first time, to create the .htpasswd file\ndocker run --rm -it \\\n  -v $(pwd)/data/nginx/conf.d/:/work \\\n  httpd:2-alpine \\\n  htpasswd -c /work/.htpasswd \u003cusername\u003e\n\n# without -c to append users\ndocker run --rm -it \\\n  -v $(pwd)/data/nginx/conf.d/:/work \\\n  httpd:2-alpine \\\n  htpasswd /work/.htpasswd \u003cusername\u003e\n\n# none interactive, add -c if you like to create a new file\ndocker run --rm \\\n  -v $(pwd)/data/nginx/conf.d/:/work \\\n  httpd:2-alpine \\\n  htpasswd -b /work/.htpasswd \u003cusername\u003e \u003cpassword\u003e\n```\n\n## Issue new SSL certificate\n\nif you like to issue a new certificate you need to setup DNS first. So the Domainname is pointing to the nginx servers\nIPv4 or IPv6 address. Then edit or create the `domains.list`:\n\n- File with domain lists (one list per line, separate domains with spaces)\n- domains.list is used as the default, or the first parameter.\n- Example:\n\n    ```txt\n    example.com www.example.com\n    example.org www.example.org blog.example.org\n    ```\n\nthen start the issue script.\n\n```bash\nchmod +x issue-from-list\n./issue-from.list.sh domains.list\n```\n\n## Renewall tests\n\nhere are some comands to check if the renewal process of certbot will work.\n\n```bash\n# all certs (dryrun only)\ndocker compose exec certbot certbot renew --dry-run\n\n# Only dry test a specific certificate (dryrun only)\ndocker compose exec certbot certbot renew --cert-name lhlab.wiki --dry-run\n\n# Force immediate testing (even if it is not yet 30 days before expiry) (dryrun only)\ndocker compose exec certbot certbot renew --cert-name lhlab.wiki --dry-run --force-renewal\n\n```\n\n## NGINX + IMGProxy Cache Test Suite `nginx_imgproxy_testing.sh`\n\nA compact Bash script to verify end‑to‑end image delivery and HTML/CDN caching for the NGINX / MediaWiki stack\n(NGINX reverse proxy + IMGProxy + MediaWiki). It prints focused headers and clear **OK/WARN/FAIL** results for each step.\n\n- Image cache warmup (**MISS → HIT**) with `X-Cache` validation\n- Content negotiation via `Accept:` (**AVIF**, **WebP**, PNG fallback)\n- Logged‑in/cache‑bypass checks (cookies, `?nocache=1`, `action=edit`, POST)\n- Validator passthrough (**ETag**, **Last‑Modified**) and optional fallback detection\n- Optional direct tests against **IMGProxy** (including **304 revalidation**)\n\n### Configuration\n\nThe script is self‑contained. Edit the **CONFIGURATION** block at the top:\n\n- `HOST`, `IMG` – target host and an existing image path\n- `BASE_URL`, `PAGE_CACHEABLE`, `PAGE_START` – pages used for HTML/CDN checks\n- `NEGATE_QS_FOR_NEG` – use image URL without query for negotiation tests (default: `1`)\n- `EXPECT_FALLBACK` – set `1` only when you intentionally stop IMGProxy to verify fallback\n- `IMGPROXY_LOCAL`, `MW_BACKEND_IMAGEURL` – enable optional direct IMGProxy checks\n\n\u003e Requirements: `bash`, `curl`\n\n## Usage\n\n```bash\nchmod +x ./nginx_imgproxy_testing.sh\n./nginx_imgproxy_testing.sh\n```\n\nThe script prints the relevant response headers and summarizes results at the end.\nA non‑zero exit code indicates at least one **FAIL**.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falaub81%2Fnginxproxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falaub81%2Fnginxproxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falaub81%2Fnginxproxy/lists"}