feat: forward tcp traffic easily
This commit is contained in:
parent
bd2e806aa1
commit
ebeb6d5c6b
@ -206,11 +206,44 @@ The role implements proper data isolation for both database backends:
|
||||
|
||||
The compose file is deployed to `{{ podman_projects_dir }}/immich/docker-compose.yml` and managed via a systemd service.
|
||||
|
||||
## Nginx Reverse Proxy with ACME/Let's Encrypt
|
||||
|
||||
The role includes an Nginx vhost template with native ACME support for automatic HTTPS certificate management.
|
||||
|
||||
**Prerequisites:**
|
||||
1. Nginx role deployed with `acme_email` configured
|
||||
2. Port 80/443 accessible from internet (for ACME HTTP-01 challenge)
|
||||
3. DNS pointing to your server
|
||||
|
||||
**Configuration:**
|
||||
```yaml
|
||||
# Enable Nginx reverse proxy
|
||||
immich_nginx_enabled: true
|
||||
immich_nginx_hostname: "blog.hello.com"
|
||||
|
||||
# In nginx role configuration (host_vars or group_vars)
|
||||
acme_email: "admin@carabosse.cloud"
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Deploys HTTPS vhost with automatic Let's Encrypt certificate
|
||||
- HTTP → HTTPS redirect
|
||||
- Proxies to Immich container on localhost
|
||||
- Handles WebSocket upgrades for live photos
|
||||
- Large file upload support (50GB max)
|
||||
|
||||
**ACME automatic features:**
|
||||
- Certificate issuance on first deployment
|
||||
- Automatic renewal
|
||||
- HTTP-01 challenge handling
|
||||
|
||||
## Post-Installation
|
||||
|
||||
After deployment:
|
||||
|
||||
1. Access Immich at `http://<host-ip>:2283`
|
||||
1. Access Immich at:
|
||||
- **With Nginx enabled**: `https://{{ immich_nginx_hostname }}`
|
||||
- **Without Nginx**: `http://<host-ip>:{{ immich_port }}`
|
||||
2. Create an admin account on first login
|
||||
3. Configure mobile/desktop apps to point to your server
|
||||
|
||||
|
||||
@ -1,7 +1,41 @@
|
||||
# Immich vhost with Let's Encrypt (Certbot)
|
||||
# Managed by Ansible - DO NOT EDIT MANUALLY
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ immich_nginx_hostname }};
|
||||
|
||||
# Certbot webroot for ACME challenges
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
# Redirect to HTTPS
|
||||
location / {
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name {{ immich_nginx_hostname }};
|
||||
|
||||
# Let's Encrypt certificates (managed by Certbot)
|
||||
ssl_certificate /etc/letsencrypt/live/{{ immich_nginx_hostname }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ immich_nginx_hostname }}/privkey.pem;
|
||||
|
||||
# SSL configuration
|
||||
ssl_protocols {{ nginx_ssl_protocols }};
|
||||
ssl_prefer_server_ciphers {{ 'on' if nginx_ssl_prefer_server_ciphers else 'off' }};
|
||||
|
||||
{% if nginx_log_backend == 'journald' %}
|
||||
access_log syslog:server=unix:/dev/log,nohostname,tag=nginx_immich;
|
||||
error_log syslog:server=unix:/dev/log,nohostname,tag=nginx_immich;
|
||||
{% else %}
|
||||
access_log /var/log/nginx/{{ immich_nginx_hostname }}_access.log main;
|
||||
error_log /var/log/nginx/{{ immich_nginx_hostname }}_error.log;
|
||||
{% endif %}
|
||||
|
||||
client_max_body_size 50000M;
|
||||
|
||||
location / {
|
||||
|
||||
@ -9,6 +9,8 @@ Installs and configures Nginx as a reverse proxy for web applications with modul
|
||||
- Configurable logging backend (journald or traditional files)
|
||||
- Automatic logrotate for file-based logging
|
||||
- SSL/TLS configuration
|
||||
- **Native ACME/Let's Encrypt support** (Nginx 1.25.0+)
|
||||
- **Transparent proxy forwarding** (HTTP/HTTPS to other hosts)
|
||||
|
||||
## Service Integration Pattern
|
||||
|
||||
@ -32,6 +34,33 @@ Each service role should deploy its own vhost config:
|
||||
notify: Reload nginx
|
||||
```
|
||||
|
||||
## Transparent Proxy Forwarding
|
||||
|
||||
Forward TCP traffic from this Nginx instance to services on other hosts using the `stream` module (layer 4 proxy).
|
||||
|
||||
**Configuration:**
|
||||
```yaml
|
||||
nginx_forwarder:
|
||||
"blog.hello.com":
|
||||
forward_to: "my.host.lan"
|
||||
http: true # Forward port 80 (default: true)
|
||||
https: true # Forward port 443 (default: true)
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
- **Stream-based TCP proxy** (layer 4, not HTTP layer 7)
|
||||
- No protocol inspection - just forwards raw TCP packets
|
||||
- **HTTPS passes through encrypted** - backend host handles TLS termination
|
||||
- HTTP also uses stream (simpler, but no HTTP features like headers/logging)
|
||||
|
||||
**Use case:** Omega (gateway) forwards all traffic to Andromeda (internal server) that handles its own TLS certificates.
|
||||
|
||||
**Important notes:**
|
||||
- Stream configs deployed to `/etc/nginx/streams.d/`
|
||||
- No HTTP logging (stream doesn't understand HTTP protocol)
|
||||
- No X-Forwarded-For headers (transparent TCP forwarding)
|
||||
- Only ONE domain can use port 443 forwarding (TCP port limitation)
|
||||
|
||||
## Logging Backends
|
||||
|
||||
**journald (default):**
|
||||
@ -64,10 +93,19 @@ tail -f /var/log/nginx/error.log
|
||||
|
||||
# List loaded vhosts
|
||||
ls -la /etc/nginx/conf.d/
|
||||
|
||||
# List stream forwarders
|
||||
ls -la /etc/nginx/streams.d/
|
||||
```
|
||||
|
||||
## Configuration Variables
|
||||
|
||||
See [defaults/main.yml](defaults/main.yml) for all available variables.
|
||||
|
||||
## References
|
||||
|
||||
- [Nginx Documentation](https://nginx.org/en/docs/)
|
||||
- [Nginx ACME Support](https://blog.nginx.org/blog/native-support-for-acme-protocol)
|
||||
- [Nginx Stream Module](https://nginx.org/en/docs/stream/ngx_stream_core_module.html)
|
||||
- [Nginx Logging](https://nginx.org/en/docs/syslog.html)
|
||||
- [Nginx SSL/TLS](https://nginx.org/en/docs/http/configuring_https_servers.html)
|
||||
- [Nginx SSL/TLS](https://nginx.org/en/docs/http/configuring_https_servers.html)
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
# Nginx configuration directory for service vhosts
|
||||
nginx_conf_dir: /etc/nginx/conf.d
|
||||
|
||||
# Nginx stream configuration directory (TCP/UDP proxies)
|
||||
nginx_streams_dir: /etc/nginx/streams.d
|
||||
|
||||
# Worker processes (auto = number of CPU cores)
|
||||
nginx_worker_processes: auto
|
||||
|
||||
@ -23,3 +26,16 @@ nginx_log_backend: journald
|
||||
nginx_logrotate_rotate: 14 # Keep 14 days of logs
|
||||
nginx_logrotate_frequency: daily # daily|weekly|monthly
|
||||
nginx_logrotate_compress: true # Compress rotated logs
|
||||
|
||||
# Forwarder configuration (transparent proxy to other hosts)
|
||||
# Example:
|
||||
# nginx_forwarder:
|
||||
# "blog.hello.com":
|
||||
# forward_to: "you.domain.org"
|
||||
# http: true # Forward port 80 (default: true)
|
||||
# https: true # Forward port 443 (default: true)
|
||||
nginx_forwarder: {}
|
||||
|
||||
# Let's Encrypt / Certbot configuration
|
||||
# acme_email: "" # Required for Let's Encrypt - intentionally undefined
|
||||
# Set this variable to enable Certbot installation and certificate management
|
||||
|
||||
@ -9,11 +9,54 @@
|
||||
ansible.builtin.set_fact:
|
||||
nginx_user: "{{ nginx_user | default('www-data') }}"
|
||||
|
||||
- name: Add Nginx official APT signing key (Debian/Ubuntu)
|
||||
ansible.builtin.get_url:
|
||||
url: https://nginx.org/keys/nginx_signing.key
|
||||
dest: /etc/apt/keyrings/nginx-archive-keyring.asc
|
||||
mode: "0644"
|
||||
when:
|
||||
- ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Add Nginx official repository (Debian/Ubuntu)
|
||||
ansible.builtin.deb822_repository:
|
||||
name: nginx-official
|
||||
types: deb
|
||||
uris: http://nginx.org/packages/mainline/debian/
|
||||
suites: "{{ ansible_facts['distribution_release'] }}"
|
||||
components: nginx
|
||||
signed_by: /etc/apt/keyrings/nginx-archive-keyring.asc
|
||||
state: present
|
||||
when:
|
||||
- ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Install nginx
|
||||
ansible.builtin.package:
|
||||
name: nginx
|
||||
state: present
|
||||
|
||||
- name: Install nginx stream module (Debian)
|
||||
ansible.builtin.package:
|
||||
name: libnginx-mod-stream
|
||||
state: present
|
||||
when:
|
||||
- ansible_facts['os_family'] == 'Debian'
|
||||
- nginx_forwarder is defined
|
||||
- nginx_forwarder | length > 0
|
||||
|
||||
- name: Install Certbot
|
||||
ansible.builtin.package:
|
||||
name: certbot
|
||||
state: present
|
||||
when: acme_email is defined
|
||||
|
||||
- name: Enable Certbot renewal timer
|
||||
ansible.builtin.systemd:
|
||||
name: certbot-renew.timer
|
||||
enabled: true
|
||||
state: started
|
||||
when: acme_email is defined
|
||||
ignore_errors: true
|
||||
|
||||
- name: Ensure nginx conf.d directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ nginx_conf_dir }}"
|
||||
@ -22,6 +65,23 @@
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Ensure nginx streams.d directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ nginx_streams_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Ensure Certbot webroot directory exists
|
||||
ansible.builtin.file:
|
||||
path: /var/www/certbot
|
||||
state: directory
|
||||
owner: "{{ nginx_user }}"
|
||||
group: "{{ nginx_user }}"
|
||||
mode: "0755"
|
||||
when: acme_email is defined
|
||||
|
||||
- name: Deploy nginx main configuration
|
||||
ansible.builtin.template:
|
||||
src: nginx.conf.j2
|
||||
@ -32,6 +92,31 @@
|
||||
validate: nginx -t -c %s
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Deploy stream forwarder configurations
|
||||
ansible.builtin.template:
|
||||
src: forwarder.conf.j2
|
||||
dest: "{{ nginx_streams_dir }}/forwarder-{{ domain | replace('.', '_') }}.conf"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop: "{{ nginx_forwarder | dict2items }}"
|
||||
loop_control:
|
||||
loop_var: item
|
||||
vars:
|
||||
domain: "{{ item.key }}"
|
||||
config: "{{ item.value }}"
|
||||
when:
|
||||
- nginx_forwarder is defined
|
||||
- nginx_forwarder | length > 0
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Validate nginx configuration after stream forwarder deployment
|
||||
ansible.builtin.command: nginx -t
|
||||
changed_when: false
|
||||
when:
|
||||
- nginx_forwarder is defined
|
||||
- nginx_forwarder | length > 0
|
||||
|
||||
- name: Deploy logrotate configuration for nginx
|
||||
ansible.builtin.template:
|
||||
src: logrotate-nginx.j2
|
||||
|
||||
25
roles/nginx/templates/forwarder.conf.j2
Normal file
25
roles/nginx/templates/forwarder.conf.j2
Normal file
@ -0,0 +1,25 @@
|
||||
# TCP stream forwarder for {{ domain }}
|
||||
# Managed by Ansible - DO NOT EDIT MANUALLY
|
||||
# Transparent TCP proxy (no protocol inspection)
|
||||
|
||||
{% if config.http | default(true) %}
|
||||
upstream {{ domain | replace('.', '_') | replace('-', '_') }}_http {
|
||||
server {{ config.forward_to }}:80;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
proxy_pass {{ domain | replace('.', '_') | replace('-', '_') }}_http;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if config.https | default(true) %}
|
||||
upstream {{ domain | replace('.', '_') | replace('-', '_') }}_https {
|
||||
server {{ config.forward_to }}:443;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
proxy_pass {{ domain | replace('.', '_') | replace('-', '_') }}_https;
|
||||
}
|
||||
{% endif %}
|
||||
@ -9,6 +9,11 @@ pid /run/nginx.pid;
|
||||
|
||||
include /usr/share/nginx/modules/*.conf;
|
||||
|
||||
{% if nginx_forwarder and nginx_forwarder | length > 0 %}
|
||||
# Load stream module for TCP/UDP proxying
|
||||
load_module modules/ngx_stream_module.so;
|
||||
{% endif %}
|
||||
|
||||
events {
|
||||
worker_connections {{ nginx_worker_connections }};
|
||||
}
|
||||
@ -48,3 +53,11 @@ http {
|
||||
# Load modular configuration files from the conf.d directory
|
||||
include {{ nginx_conf_dir }}/*.conf;
|
||||
}
|
||||
|
||||
{% if nginx_forwarder and nginx_forwarder | length > 0 %}
|
||||
# Stream block for TCP/UDP proxying
|
||||
stream {
|
||||
# Load stream configurations
|
||||
include {{ nginx_streams_dir }}/*.conf;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
41
roles/nginx/templates/vhost-acme.conf.j2
Normal file
41
roles/nginx/templates/vhost-acme.conf.j2
Normal file
@ -0,0 +1,41 @@
|
||||
# HTTPS vhost with Let's Encrypt (Certbot) for {{ server_name }}
|
||||
# Managed by Ansible - DO NOT EDIT MANUALLY
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ server_name }};
|
||||
|
||||
# Certbot webroot for ACME challenges
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
# Redirect to HTTPS
|
||||
location / {
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name {{ server_name }};
|
||||
|
||||
# Let's Encrypt certificates (managed by Certbot)
|
||||
ssl_certificate /etc/letsencrypt/live/{{ server_name }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ server_name }}/privkey.pem;
|
||||
|
||||
# SSL configuration
|
||||
ssl_protocols {{ nginx_ssl_protocols }};
|
||||
ssl_prefer_server_ciphers {{ 'on' if nginx_ssl_prefer_server_ciphers else 'off' }};
|
||||
|
||||
{% if nginx_log_backend == 'journald' %}
|
||||
access_log syslog:server=unix:/dev/log,nohostname,tag=nginx_{{ server_name | replace('.', '_') }};
|
||||
error_log syslog:server=unix:/dev/log,nohostname,tag=nginx_{{ server_name | replace('.', '_') }};
|
||||
{% else %}
|
||||
access_log /var/log/nginx/{{ server_name }}_access.log main;
|
||||
error_log /var/log/nginx/{{ server_name }}_error.log;
|
||||
{% endif %}
|
||||
|
||||
# Service-specific configuration included below
|
||||
{{ vhost_config | default('') }}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user