Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save bertmelis/aa7f460765720bf33d10cf6231579542 to your computer and use it in GitHub Desktop.

Select an option

Save bertmelis/aa7f460765720bf33d10cf6231579542 to your computer and use it in GitHub Desktop.
Immich behind nginx proxy with Letsencrypt certificates
  • !! underscore _ in filename == slash /
  • I'm using podman instead of Docker. Systemd files were generated by podman (not quadlet).
  • certbot image has to be pulled manually
  • before first start, a certificate has to be requested. Nginx won't start if it's not there. You can request one using certbot in standalone mode: podman run -it --rm -p 80:80 -v ~/nginx/data/letsencrypt:/etc/letsencrypt:z -v ~/nginx/log:/var/log:z certbot/certbot certonly --standalone --staging --dry-run --key-type ecdsa --rsa-key-size 4096 -d immich.domain.tld
  • ports forwarded and opened: 80/TCP, 443/UDP and 443/TCP

directory structure has to look like this. directories and logfiles may have to be created manually. check startup errors.

$ home - nginx
           |
           +- conf
           |    |
           |    + nginx.conf
           |    + http3.conf
           |    + letsencrypt-acme-challenge.conf
           |    +- conf.d
           |         |
           |         + sub.domain.tld.conf
           +- data
           |   |
           |   +- letsencrypt
           |   +- www
           +- log
               |
               +- letsencrypt
               +- nginx
                    |
                    + access.log
                    + error.log
-----BEGIN DH PARAMETERS-----
[redacted]
-----END DH PARAMETERS-----
# container-nginx.service
[Unit]
Description=Podman container-nginx.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
# container-nginx.service
# autogenerated by Podman 4.4.1
# Sat Jun 10 14:17:51 CEST 2023
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=unless-stopped
TimeoutStopSec=70
ExecStart=/usr/bin/podman run \
--cidfile=%t/%n.ctr-id \
--cgroups=no-conmon \
--rm \
--sdnotify=conmon \
--replace \
--detach \
--name=nginx \
--label io.containers.autoupdate=registry \
-v /home/podmanager/nginx/conf:/etc/nginx:Z,ro \
-v /home/podmanager/nginx/log:/var/log:z \
-v /home/podmanager/nginx/data/www:/srv:z,ro \
-v /home/podmanager/nginx/data/letsencrypt:/letsencrypt:z,ro \
--network host docker.io/library/nginx:latest
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
-f \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all
[Install]
WantedBy=default.target
[Unit]
Description=Renew Let's Encrypt certificates
After=container-nginx.service
[Service]
Type=oneshot
ExecStart=podman run -it --rm -v %h/nginx/data/letsencrypt:/etc/letsencrypt:z -v %h/nginx/data/www/letsencrypt:/srv/letsencrypt:z -v %h/nginx/log:/var/log:z certbot/certbot renew --webroot -w /srv/letsencrypt
ExecStart=podman exec nginx nginx -s reload
[Install]
WantedBy=default.target
[Unit]
Description=Renew Let's Encrypt certificates daily at 01:30
[Timer]
OnCalendar=*-*-* 01:30:00
[Install]
WantedBy=timers.target
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
map $http_x_forwarded_proto $forwarded_protocol {
default $scheme;
# Only allow the values 'http' and 'https' for the X-Forwarded-Proto header.
http http;
https https;
}
upstream immich_server {
server 127.0.0.1:3001;
keepalive 2;
}
server {
listen 80;
server_name immich.domain.tld;
return 301 https://$host$request_uri;
}
server {
# when adding additional server blocks, omit 'reuseport'
# listen 443 quic
listen 443 quic reuseport;
listen 443 ssl;
http2 on;
server_name immich.domain.tld;
# use a variable to store the upstream proxy
# in this example we are using a hostname which is resolved via DNS
# (if you aren't using DNS remove the resolver line and change the variable to point to an IP address e.g `set $immich 127.0.0.1`)
#resolver 127.0.0.1 valid=30;
include /etc/nginx/http3.conf;
ssl_certificate /letsencrypt/live/immich.domain.tld/fullchain.pem;
ssl_certificate_key /letsencrypt/live/immich.domain.tld/privkey.pem;
ssl_trusted_certificate /letsencrypt/live/immich.domain.tld/chain.pem;
# Security / XSS Mitigation Headers
# NOTE: X-Frame-Options may cause issues with the webOS app
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
# Content Security Policy
# See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
# Enforces https content and restricts JS/CSS to origin
# External Javascript (such as cast_sender.js for Chromecast) must be whitelisted.
# NOTE: The default CSP headers may cause issues with the webOS app
#add_header Content-Security-Policy "default-src https: data: blob: http://image.tmdb.org; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com/cv/js/sender/v1/cast_sender.js https://www.gstatic.com/eureka/clank/95/cast_sender.js https://www.gstatic.com/eureka/clank/96/cast_sender.js https://www.gstatic.com/eureka/clank/97/cast_sender.js https://www.youtube.com blob:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'";
# enable Letsencrypt validation
include /etc/nginx/letsencrypt-acme-challenge.conf;
# Disable gzip --> BREACH
#gzip off;
#gzip_comp_level 2;
#gzip_min_length 1000;
#gzip_proxied any;
#gzip_vary on;
#gunzip on;
# text/html is included by default
#gzip_types
# application/javascript
# application/json
# font/ttf
# image/svg+xml
# text/css;
client_max_body_size 50000M;
location / {
proxy_buffering off;
proxy_request_buffering off;
proxy_buffer_size 16k;
proxy_busy_buffers_size 24k;
proxy_buffers 64 4k;
proxy_force_ranges on;
proxy_set_header Host $host;
proxy_redirect http:// https://;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
#proxy_http_version 1.1;
#proxy_set_header Host $http_host;
#proxy_set_header X-Forwarded-Host $http_host;
#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#proxy_set_header X-Forwarded-Proto $forwarded_protocol;
#proxy_set_header Upgrade $http_upgrade;
#proxy_set_header Connection $connection_upgrade;
proxy_pass http://immich_server;
}
}
ssl_protocols TLSv1.2 TLSv1.3; # TLSv1.3 necessary for http3
quic_retry on;
ssl_early_data on;
#ssl_prefer_server_ciphers on; # Let the client choose, as of 2022-05 these ciphers are all still secure.
ssl_dhparam /etc/nginx/dhparams4096.pem; # generate with 'openssl dhparam -out dhparam.pem 4096'
ssl_ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256: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;
ssl_ecdh_curve X25519:prime256v1:secp384r1;
ssl_session_cache shared:SSL:5m;
ssl_session_timeout 1h;
ssl_session_tickets off;
ssl_buffer_size 4k; # This is for performance rather than security, the optimal value depends on each site.
# 16k default, 4k is a good first guess and likely more performant.
ssl_stapling on; # As of 2022-05 this version of nginx doesn't support ssl-stapling, but it might be in the future.
ssl_stapling_verify on;
#resolver 127.0.0.1 192.168.130.10 8.8.8.8 valid=300s; # Use whichever resolvers you'd like, these are Cloudflare's and is one of the fastest DNS resolvers.
resolver 8.8.8.8 valid=300s;
resolver_timeout 5s;
# use per domain certificates
#ssl_certificate /my-path/fullchain.pem;
#ssl_certificate_key /my-path/privkey.pem;
#ssl_trusted_certificate /my-path/chain.pem; # For SSL-stapling
proxy_request_buffering off;
add_header Alt-Svc 'h3=":443"; ma=86400'; # Absolutely necessary header. This informs the client that HTTP/3 is available.
add_header Strict-Transport-Security max-age=15768000; # Optional but good, client should always try to use HTTPS, even for initial requests.
gzip off; #https://en.wikipedia.org/wiki/BREACH
#############################################################################
# Configuration file for Let's Encrypt ACME Challenge location
# This file is already included in listen_xxx.conf files.
# Do NOT include it separately!
#############################################################################
#
# This config enables to access /.well-known/acme-challenge/xxxxxxxxxxx
# on all our sites (HTTP), including all subdomains.
# This is required by ACME Challenge (webroot authentication).
# You can check that this location is working by placing ping.txt here:
# /var/www/letsencrypt/.well-known/acme-challenge/ping.txt
# And pointing your browser to:
# http://xxx.domain.tld/.well-known/acme-challenge/ping.txt
#
# Sources:
# https://community.letsencrypt.org/t/howto-easy-cert-generation-and-renewal-with-nginx/3491
#
#############################################################################
# Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
# We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel
# other regex checks, because in our other config files have regex rule that denies access to files with dotted names.
location ^~ /.well-known/acme-challenge/ {
# Set correct content type. According to this:
# https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29
# Current specification requires "text/plain" or no content header at all.
# It seems that "text/plain" is a safe option.
default_type "text/plain";
# This directory must be the same as in /etc/letsencrypt/cli.ini
# as "webroot-path" parameter. Also don't forget to set "authenticator" parameter
# there to "webroot".
# Do NOT use alias, use root! Target directory is located here:
# /var/www/letsencrypt/.well-known/acme-challenge/
root /srv/letsencrypt;
}
# Hide /acme-challenge subdirectory and return 404 on all requests.
# It is somewhat more secure than letting Nginx return 403.
# Ending slash is important!
location = /.well-known/acme-challenge/ {
return 404;
}
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/avif avif;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/wasm wasm;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}
#user nobody;
worker_processes 4;
#error_log logs/error.log;
#error_log logs/error.log notice;
error_log /var/log/nginx/error.log warn;
#error_log /var/log/nginx/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
log_format quic '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$http3"';
#access_log /var/log/nginx/access.log quic;
#access_log /var/log/nginx/access1.log main;
#server {
# listen 80 default_server;
# server_name local.emelis.net;
# return 301 https://$host$request_uri;
#}
#server {
# listen 443 http3 reuseport default_server;
# listen 443 ssl http2;
# include http3.conf;
# server_name local.emelis.net;
# root /srv;
#ssl_certificate /local/local.emelis.net.cert.pem;
#ssl_certificate_key /local/local.emelis.net.privkey.pem;
#}
include /etc/nginx/conf.d/*.conf;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment