Arquitectura
Client ──HTTPS(443)──► Nginx (host)
├─► / ─► proxy_pass http://127.0.0.1:8080 → web8080 (nginx:alpine, volum: /data/www → /usr/share/nginx/html)
└─► /portainer/ ─► proxy_pass http://127.0.0.1:9000/ → portainer (HTTP intern 9000, --base-url /portainer)
Docker
Nginx
Certbot / Let's Encrypt
Reverse Proxy
WebSockets
Objectiu didàctic: separar responsabilitats entre proxy (Nginx), serveis en contenidor (web estàtic i Portainer) i gestió de certificats (Certbot).
Prèvies
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo ufw allow 80,443/tcp
Ports oberts: 80/tcp 443/tcp
Contenidors Docker
1) Web estàtic (port 8080) amb volum
sudo mkdir -p /data/www
echo '<h1>Hola dam0!</h1>' | sudo tee /data/www/index.html
sudo docker run -d \
--name web8080 \
--restart=always \
-p 8080:80 \
-v /data/www:/usr/share/nginx/html:ro \
nginx:alpine
2) Portainer sota subruta /portainer (HTTP intern)
sudo mkdir -p /data/portainer
# (recrear si cal)
sudo docker stop portainer 2>/dev/null || true
sudo docker rm portainer 2>/dev/null || true
sudo docker run -d \
--name portainer \
--restart=always \
-p 8000:8000 \
-p 9000:9000 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /data/portainer:/data \
portainer/portainer-ce:latest \
--http-enabled \
--base-url /portainer
A Portainer → Settings → Public URL:
https://dam0.dam.inspedralbes.cat/portainerEstat esperat (exemple)
docker ps --format "table {{.Names}}\t{{.Ports}}"
NAMES PORTS
portainer 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp, 0.0.0.0:9000->9000/tcp, :::9000->9000/tcp, 9443/tcp
web8080 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
Configuració Nginx (proxy invers)
Fitxer: /etc/nginx/sites-available/dam0.dam.inspedralbes.cat (enllaç a sites-enabled)
server {
listen 443;
server_name dam0.dam.inspedralbes.cat;
# Arrel → contenidor web (8080)
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Redirecció /portainer → /portainer/
location = /portainer {
return 301 /portainer/;
}
# Portainer sota subruta (HTTP backend 9000)
location ^~ /portainer/ {
proxy_pass http://127.0.0.1:9000/; # barra final per fer strip del prefix
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
}
# WebSocket Portainer (terminal, logs live)
location ^~ /portainer/api/websocket/ {
proxy_pass http://127.0.0.1:9000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
ssl_certificate /etc/letsencrypt/live/dam0.dam.inspedralbes.cat/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/dam0.dam.inspedralbes.cat/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = dam0.dam.inspedralbes.cat) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name dam0.dam.inspedralbes.cat;
return 404; # managed by Certbot
}
# activar site i desactivar el per defecte
sudo ln -sf /etc/nginx/sites-available/dam0.dam.inspedralbes.cat /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
Certificat digital (Let's Encrypt amb Certbot)
sudo certbot --nginx -d dam0.dam.inspedralbes.cat --redirect -m pprats@inspedralbes.cat --agree-tos
Certbot crea i renova automàticament el certificat (timers del sistema). Prova de renovació:
sudo certbot renew --dry-run
Verificacions
# local (HTTP)
curl -I http://127.0.0.1/
curl -I http://127.0.0.1/portainer/
# públic (HTTPS)
curl -I https://dam0.dam.inspedralbes.cat/
curl -I https://dam0.dam.inspedralbes.cat/portainer/
Exemple de respostes correctes
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
...
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
# (Portainer servit via subruta)
...
Checklist de ports i firewall
# qui escolta a 80/443?
sudo ss -lntp | grep -E ':(80|443)\b' || echo "Res escoltant a 80/443"
# UFW
sudo ufw status verbose
sudo ufw allow 80,443/tcp
sudo ufw reload
Troubleshooting
1) 404 a /portainer/
- Assegura
--base-url /portainera Portainer. - El bloc
location ^~ /portainer/ha d’usarproxy_pass http://127.0.0.1:9000/(amb / final). - A Portainer → Settings → Public URL =
https://dam0.dam.inspedralbes.cat/portainer.
2) Terminal/logs no funcionen
- Comprova el bloc WebSocket (
/portainer/api/websocket/), capçaleresUpgradeiConnection.
3) HTTPS no connecta des de fora
ss -lntpha de mostrar Nginx escoltant a 443.- Revisa UFW/NAT/seguretat del proveïdor: obrir 443 cap al servidor.
- DNS del domini apunta a la IP pública correcta.
4) Comentaris en comandes multi-línia
- No posis comentaris (
# ...) al mig d’una comanda amb\. - Alternativa: comanda en una sola línia.
Scripts “tot en un” (opcional per aula)
Deploy bàsic
#!/usr/bin/env bash
set -euo pipefail
# Paquets i firewall
apt update
apt install -y nginx certbot python3-certbot-nginx
ufw allow 80,443/tcp || true
# Web 8080
mkdir -p /data/www
echo '<h1>Hola dam0!</h1>' > /data/www/index.html
docker run -d --name web8080 --restart=always -p 8080:80 -v /data/www:/usr/share/nginx/html:ro nginx:alpine
# Portainer HTTP a /portainer
mkdir -p /data/portainer
docker rm -f portainer 2>/dev/null || true
docker run -d --name portainer --restart=always \
-p 8000:8000 -p 9000:9000 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /data/portainer:/data \
portainer/portainer-ce:latest \
--http-enabled --base-url /portainer
# Nginx (site)
cat >/etc/nginx/sites-available/dam0.dam.inspedralbes.cat <<'CONF'
server {
listen 80;
server_name dam0.dam.inspedralbes.cat;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /portainer { return 301 /portainer/; }
location ^~ /portainer/ {
proxy_pass http://127.0.0.1:9000/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
}
location ^~ /portainer/api/websocket/ {
proxy_pass http://127.0.0.1:9000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
CONF
ln -sf /etc/nginx/sites-available/dam0.dam.inspedralbes.cat /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx
# Certbot
certbot --nginx -d dam0.dam.inspedralbes.cat --redirect -m pprats@inspedralbes.cat --agree-tos
Ajusta domini/email si cal.
Apèndix: snippets útils
# comprovar serveis al 80/443
sudo ss -lntp | grep -E ':(80|443)\b' || echo "Res escoltant"
# logs Nginx
sudo journalctl -u nginx -n 200 --no-pager
# recrear Portainer si canvies la base-url
docker rm -f portainer
docker run -d --name portainer --restart=always \
-p 8000:8000 -p 9000:9000 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /data/portainer:/data \
portainer/portainer-ce:latest \
--http-enabled --base-url /portainer
Crèdits i versions
- Nginx (host) — 1.24.x (Ubuntu/Debian repo)
- Certbot — python3-certbot-nginx
- Docker images —
nginx:alpine,portainer/portainer-ce:latest