1275 lines
30 KiB
Markdown
1275 lines
30 KiB
Markdown
# Guía de Migración: Nginx Proxy Manager → Traefik
|
|
|
|
**Fecha**: 5 de Octubre, 2025
|
|
**Servidor**: nucleo002
|
|
**Configuraciones Extraídas**: 18 Proxy Hosts
|
|
|
|
---
|
|
|
|
## 📋 Índice
|
|
|
|
1. [Resumen de Configuraciones NPM](#resumen-de-configuraciones-npm)
|
|
2. [Configuración Base de Traefik](#configuración-base-de-traefik)
|
|
3. [Traducción de Cada Proxy Host](#traducción-de-cada-proxy-host)
|
|
4. [Middlewares Comunes](#middlewares-comunes)
|
|
5. [Certificados SSL](#certificados-ssl)
|
|
6. [Docker Compose Completo](#docker-compose-completo)
|
|
7. [Scripts de Migración](#scripts-de-migración)
|
|
|
|
---
|
|
|
|
## 1. Resumen de Configuraciones NPM
|
|
|
|
### Proxy Hosts Detectados
|
|
|
|
| ID | Dominio | Backend | Puerto | SSL | Características Especiales |
|
|
|----|---------|---------|--------|-----|----------------------------|
|
|
| 2 | portainer.interno.com | portainer | 9443 | Custom | - |
|
|
| 3 | gitea.interno.com | gitea | 3000 | Custom | - |
|
|
| 4 | geoids.interno.com | geo-ids | 443 | Custom | **Authentik SSO** |
|
|
| 5 | amigos.nucleoriofrio.com | amigos-app | 3001 | Let's Encrypt | **Authentik SSO** (parcial) |
|
|
| 7 | whatsappbot.interno.com | whatsapp-router | 3001 | Custom | - |
|
|
| 8 | conversation-layer.interno.com | - | - | Custom | - |
|
|
| 9 | Nginx.interno.com | npm | 81 | Custom | Admin Panel |
|
|
| 10 | planilla.interno.com | planilla-ui | 80 | Custom | **Multi-location** (/, /api, /mcp, /events) |
|
|
| 11 | nucleobot.interno.com | whatsapp-router | 3001 | Custom | - |
|
|
| 16 | nucleoriofrio.com | z590.interno.com | 3000 | Let's Encrypt | **Custom locations** (/api/chat/stream, /api/chat/) |
|
|
| 17 | musica.interno.com | z590.interno.com | 3000 | Custom | **HTTP/2**, **HMR**, Multiple locations |
|
|
| 18 | musica.nucleoriofrio.com | repodructor | 3000 | Let's Encrypt | Multiple locations, Cache headers |
|
|
| 19 | snatchgame.interno.com | z590.interno.com | 3004 | Custom | **7 locations** (/ws, /colyseus, /api/*) |
|
|
| 20 | snatchgame.nucleoriofrio.com | snatchgame | 2567 | Let's Encrypt | **6 locations**, Asset caching |
|
|
| 21 | duplicati.interno.com | duplicati | - | Custom | - |
|
|
| 22 | internet.nucleoriofrio.com | z590.interno.com | 80 | Let's Encrypt | - |
|
|
| 23 | wifi.nucleoriofrio.com | radiusnucleo-node-1 | - | Let's Encrypt | - |
|
|
|
|
### Características Globales
|
|
|
|
- **HSTS**: Habilitado en todos (`max-age=63072000; preload`)
|
|
- **Force SSL**: Mayoría de hosts
|
|
- **Block Exploits**: Todos
|
|
- **WebSocket Support**: Habilitado donde se necesita
|
|
- **HTTP/2**: Solo musica.interno.com
|
|
|
|
---
|
|
|
|
## 2. Configuración Base de Traefik
|
|
|
|
### 2.1 Docker Compose - Traefik
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
traefik:
|
|
image: traefik:v3.1
|
|
container_name: traefik
|
|
restart: unless-stopped
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
networks:
|
|
- principal
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
- "8080:8080" # Dashboard
|
|
environment:
|
|
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN} # Si usas Cloudflare
|
|
- CF_ZONE_API_TOKEN=${CF_ZONE_API_TOKEN}
|
|
volumes:
|
|
- /etc/localtime:/etc/localtime:ro
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- ./traefik.yml:/traefik.yml:ro
|
|
- ./config/:/config/:ro
|
|
- ./acme.json:/acme.json
|
|
- ./certificates/:/certificates/:ro
|
|
labels:
|
|
- "traefik.enable=true"
|
|
|
|
# Dashboard
|
|
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.interno.com`)"
|
|
- "traefik.http.routers.traefik-dashboard.entrypoints=https"
|
|
- "traefik.http.routers.traefik-dashboard.tls=true"
|
|
- "traefik.http.routers.traefik-dashboard.service=api@internal"
|
|
- "traefik.http.routers.traefik-dashboard.middlewares=auth@file"
|
|
|
|
networks:
|
|
principal:
|
|
external: true
|
|
```
|
|
|
|
### 2.2 traefik.yml (Static Configuration)
|
|
|
|
```yaml
|
|
api:
|
|
dashboard: true
|
|
debug: false
|
|
|
|
entryPoints:
|
|
http:
|
|
address: ":80"
|
|
http:
|
|
redirections:
|
|
entryPoint:
|
|
to: https
|
|
scheme: https
|
|
permanent: true
|
|
|
|
https:
|
|
address: ":443"
|
|
http:
|
|
tls:
|
|
options: default
|
|
certResolver: letsencrypt
|
|
domains:
|
|
- main: "*.interno.com"
|
|
- main: "*.nucleoriofrio.com"
|
|
http2:
|
|
maxConcurrentStreams: 250
|
|
|
|
metrics:
|
|
address: ":8082"
|
|
|
|
providers:
|
|
docker:
|
|
endpoint: "unix:///var/run/docker.sock"
|
|
exposedByDefault: false
|
|
network: principal
|
|
watch: true
|
|
|
|
file:
|
|
directory: /config
|
|
watch: true
|
|
|
|
certificatesResolvers:
|
|
letsencrypt:
|
|
acme:
|
|
email: admin@nucleoriofrio.com
|
|
storage: /acme.json
|
|
httpChallenge:
|
|
entryPoint: http
|
|
# O para wildcard con DNS challenge:
|
|
# dnsChallenge:
|
|
# provider: cloudflare
|
|
# delayBeforeCheck: 0
|
|
|
|
custom:
|
|
acme:
|
|
tlsChallenge: {}
|
|
storage: /acme.json
|
|
|
|
# Global HTTP settings
|
|
serversTransport:
|
|
insecureSkipVerify: true # Solo si usas certificados autofirmados en backend
|
|
|
|
# Logging
|
|
log:
|
|
level: INFO
|
|
filePath: /logs/traefik.log
|
|
format: json
|
|
|
|
accessLog:
|
|
filePath: /logs/access.log
|
|
format: json
|
|
bufferingSize: 100
|
|
|
|
# Metrics (opcional)
|
|
metrics:
|
|
prometheus:
|
|
addEntryPointsLabels: true
|
|
addServicesLabels: true
|
|
entryPoint: metrics
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Traducción de Cada Proxy Host
|
|
|
|
### 3.1 Portainer (portainer.interno.com)
|
|
|
|
**NPM Original**:
|
|
- Backend: `portainer:9443` (HTTPS)
|
|
- SSL: Custom certificate
|
|
- Force SSL
|
|
|
|
**Traefik Labels**:
|
|
|
|
```yaml
|
|
# En docker-compose del stack portainer
|
|
services:
|
|
portainer:
|
|
# ... resto de config
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.portainer.rule=Host(`portainer.interno.com`)"
|
|
- "traefik.http.routers.portainer.entrypoints=https"
|
|
- "traefik.http.routers.portainer.tls=true"
|
|
- "traefik.http.routers.portainer.tls.certresolver=custom"
|
|
|
|
- "traefik.http.services.portainer.loadbalancer.server.port=9443"
|
|
- "traefik.http.services.portainer.loadbalancer.server.scheme=https"
|
|
|
|
# Middlewares
|
|
- "traefik.http.routers.portainer.middlewares=hsts-headers,block-exploits"
|
|
networks:
|
|
- principal
|
|
```
|
|
|
|
---
|
|
|
|
### 3.2 Gitea (gitea.interno.com)
|
|
|
|
**NPM Original**:
|
|
- Backend: `gitea:3000` (HTTP)
|
|
- SSL: Custom
|
|
- No Force SSL
|
|
|
|
**Traefik Labels**:
|
|
|
|
```yaml
|
|
services:
|
|
gitea:
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.gitea.rule=Host(`gitea.interno.com`)"
|
|
- "traefik.http.routers.gitea.entrypoints=https"
|
|
- "traefik.http.routers.gitea.tls=true"
|
|
- "traefik.http.routers.gitea.tls.certresolver=custom"
|
|
|
|
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
|
|
|
|
- "traefik.http.routers.gitea.middlewares=hsts-headers,block-exploits"
|
|
networks:
|
|
- principal
|
|
```
|
|
|
|
---
|
|
|
|
### 3.3 GeoIDs con Authentik SSO (geoids.interno.com)
|
|
|
|
**NPM Original**:
|
|
- Backend: `geo-ids:443` (HTTPS)
|
|
- Auth: Authentik via `auth_request`
|
|
- WebSocket support
|
|
- Custom buffers
|
|
|
|
**Traefik Labels + Middleware File**:
|
|
|
|
```yaml
|
|
services:
|
|
geo-ids:
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.geoids.rule=Host(`geoids.interno.com`)"
|
|
- "traefik.http.routers.geoids.entrypoints=https"
|
|
- "traefik.http.routers.geoids.tls=true"
|
|
- "traefik.http.routers.geoids.tls.certresolver=custom"
|
|
|
|
- "traefik.http.services.geoids.loadbalancer.server.port=443"
|
|
- "traefik.http.services.geoids.loadbalancer.server.scheme=https"
|
|
|
|
# Middlewares (orden importa!)
|
|
- "traefik.http.routers.geoids.middlewares=authentik-geoids@file,hsts-headers,websocket-headers"
|
|
networks:
|
|
- principal
|
|
```
|
|
|
|
**Middleware File** (`/config/middlewares-authentik.yml`):
|
|
|
|
```yaml
|
|
http:
|
|
middlewares:
|
|
authentik-geoids:
|
|
forwardAuth:
|
|
address: "http://authentik-server:9000/outpost.goauthentik.io/auth/nginx"
|
|
trustForwardHeader: true
|
|
authResponseHeaders:
|
|
- "X-authentik-username"
|
|
- "X-authentik-groups"
|
|
- "X-authentik-email"
|
|
- "X-authentik-name"
|
|
- "X-authentik-uid"
|
|
- "Set-Cookie"
|
|
```
|
|
|
|
---
|
|
|
|
### 3.4 Planilla Multi-Location (planilla.interno.com)
|
|
|
|
**NPM Original**:
|
|
- `/` → `planilla-ui:80`
|
|
- `/api` → `planilla-api:4000`
|
|
- `/mcp` → `planilla-mcp:5000`
|
|
- `/events` → `planilla-api:4000`
|
|
- WebSocket en todos
|
|
|
|
**Traefik Dynamic Config** (`/config/planilla.yml`):
|
|
|
|
```yaml
|
|
http:
|
|
routers:
|
|
planilla-ui:
|
|
rule: "Host(`planilla.interno.com`)"
|
|
entryPoints:
|
|
- https
|
|
service: planilla-ui-svc
|
|
middlewares:
|
|
- hsts-headers
|
|
- websocket-headers
|
|
tls:
|
|
certResolver: custom
|
|
priority: 1
|
|
|
|
planilla-api:
|
|
rule: "Host(`planilla.interno.com`) && PathPrefix(`/api`)"
|
|
entryPoints:
|
|
- https
|
|
service: planilla-api-svc
|
|
middlewares:
|
|
- hsts-headers
|
|
- websocket-headers
|
|
tls:
|
|
certResolver: custom
|
|
priority: 10
|
|
|
|
planilla-mcp:
|
|
rule: "Host(`planilla.interno.com`) && PathPrefix(`/mcp`)"
|
|
entryPoints:
|
|
- https
|
|
service: planilla-mcp-svc
|
|
middlewares:
|
|
- hsts-headers
|
|
- websocket-headers
|
|
tls:
|
|
certResolver: custom
|
|
priority: 10
|
|
|
|
planilla-events:
|
|
rule: "Host(`planilla.interno.com`) && PathPrefix(`/events`)"
|
|
entryPoints:
|
|
- https
|
|
service: planilla-api-svc # Mismo que /api
|
|
middlewares:
|
|
- hsts-headers
|
|
- websocket-headers
|
|
tls:
|
|
certResolver: custom
|
|
priority: 10
|
|
|
|
services:
|
|
planilla-ui-svc:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://planilla-ui:80"
|
|
|
|
planilla-api-svc:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://planilla-api:4000"
|
|
|
|
planilla-mcp-svc:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://planilla-mcp:5000"
|
|
```
|
|
|
|
---
|
|
|
|
### 3.5 Música con HMR (musica.interno.com)
|
|
|
|
**NPM Original**:
|
|
- HTTP/2: ON
|
|
- Multiple locations: `/_nuxt/`, `/__nuxt_devtools__/`, `/api/`
|
|
- WebSocket para HMR
|
|
- Proxy buffering OFF
|
|
- Large client body
|
|
|
|
**Traefik Config** (`/config/musica.yml`):
|
|
|
|
```yaml
|
|
http:
|
|
routers:
|
|
musica-nuxt:
|
|
rule: "Host(`musica.interno.com`) && PathPrefix(`/_nuxt/`)"
|
|
entryPoints:
|
|
- https
|
|
service: musica-z590-svc
|
|
middlewares:
|
|
- websocket-headers
|
|
- no-buffering
|
|
tls:
|
|
certResolver: custom
|
|
priority: 20
|
|
|
|
musica-devtools:
|
|
rule: "Host(`musica.interno.com`) && PathPrefix(`/__nuxt_devtools__/`)"
|
|
entryPoints:
|
|
- https
|
|
service: musica-z590-svc
|
|
middlewares:
|
|
- websocket-headers
|
|
- no-buffering
|
|
tls:
|
|
certResolver: custom
|
|
priority: 20
|
|
|
|
musica-api:
|
|
rule: "Host(`musica.interno.com`) && PathPrefix(`/api/`)"
|
|
entryPoints:
|
|
- https
|
|
service: musica-z590-svc
|
|
middlewares:
|
|
- no-buffering
|
|
- large-timeout
|
|
tls:
|
|
certResolver: custom
|
|
priority: 15
|
|
|
|
musica-root:
|
|
rule: "Host(`musica.interno.com`)"
|
|
entryPoints:
|
|
- https
|
|
service: musica-z590-svc
|
|
middlewares:
|
|
- hsts-headers
|
|
tls:
|
|
certResolver: custom
|
|
priority: 1
|
|
|
|
services:
|
|
musica-z590-svc:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://192.168.87.135:3000" # z590.interno.com
|
|
passHostHeader: true
|
|
|
|
middlewares:
|
|
no-buffering:
|
|
buffering:
|
|
maxRequestBodyBytes: 104857600 # 100MB
|
|
memRequestBodyBytes: 104857600
|
|
maxResponseBodyBytes: 0 # Sin límite
|
|
memResponseBodyBytes: 0
|
|
retryExpression: "IsNetworkError() && Attempts() < 2"
|
|
|
|
large-timeout:
|
|
buffering:
|
|
maxRequestBodyBytes: 104857600
|
|
# Los timeouts se configuran en service o entrypoint
|
|
```
|
|
|
|
---
|
|
|
|
### 3.6 SnatchGame Complejo (snatchgame.interno.com)
|
|
|
|
**NPM Original**:
|
|
- 7 locations diferentes
|
|
- WebSocket en todos
|
|
- Asset caching
|
|
|
|
**Traefik Config** (`/config/snatchgame.yml`):
|
|
|
|
```yaml
|
|
http:
|
|
routers:
|
|
snatch-ws:
|
|
rule: "Host(`snatchgame.interno.com`) && PathPrefix(`/ws`)"
|
|
entryPoints:
|
|
- https
|
|
service: snatch-z590-svc
|
|
middlewares:
|
|
- websocket-headers
|
|
- asset-cache
|
|
tls:
|
|
certResolver: custom
|
|
priority: 30
|
|
|
|
snatch-colyseus:
|
|
rule: "Host(`snatchgame.interno.com`) && PathPrefix(`/colyseus`)"
|
|
entryPoints:
|
|
- https
|
|
service: snatch-z590-svc
|
|
middlewares:
|
|
- websocket-headers
|
|
- asset-cache
|
|
tls:
|
|
certResolver: custom
|
|
priority: 25
|
|
|
|
snatch-api-stream:
|
|
rule: "Host(`snatchgame.interno.com`) && Path(`/api/dashboard-stream`)"
|
|
entryPoints:
|
|
- https
|
|
service: snatch-z590-svc
|
|
middlewares:
|
|
- websocket-headers
|
|
tls:
|
|
certResolver: custom
|
|
priority: 40
|
|
|
|
snatch-api-uuids-stream:
|
|
rule: "Host(`snatchgame.interno.com`) && Path(`/api/uuids-stream`)"
|
|
entryPoints:
|
|
- https
|
|
service: snatch-z590-svc
|
|
middlewares:
|
|
- websocket-headers
|
|
tls:
|
|
certResolver: custom
|
|
priority: 40
|
|
|
|
snatch-api-admin:
|
|
rule: "Host(`snatchgame.interno.com`) && PathPrefix(`/api/admin/`)"
|
|
entryPoints:
|
|
- https
|
|
service: snatch-z590-svc
|
|
middlewares:
|
|
- asset-cache
|
|
tls:
|
|
certResolver: custom
|
|
priority: 35
|
|
|
|
snatch-api-wildcard:
|
|
rule: "Host(`snatchgame.interno.com`) && PathRegexp(`^/api/.*`)"
|
|
entryPoints:
|
|
- https
|
|
service: snatch-z590-svc
|
|
middlewares:
|
|
- asset-cache
|
|
tls:
|
|
certResolver: custom
|
|
priority: 20
|
|
|
|
snatch-root:
|
|
rule: "Host(`snatchgame.interno.com`)"
|
|
entryPoints:
|
|
- https
|
|
service: snatch-z590-svc
|
|
middlewares:
|
|
- hsts-headers
|
|
- asset-cache
|
|
tls:
|
|
certResolver: custom
|
|
priority: 1
|
|
|
|
services:
|
|
snatch-z590-svc:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://z590.interno.com:3004"
|
|
```
|
|
|
|
---
|
|
|
|
### 3.7 Nucleoriofrio.com con API Chat
|
|
|
|
**NPM Original**:
|
|
- `/api/chat/stream` → Streaming con buffering OFF
|
|
- `/api/chat/` → API normal
|
|
- CORS headers
|
|
- Large timeouts
|
|
|
|
**Traefik Config** (`/config/nucleoriofrio.yml`):
|
|
|
|
```yaml
|
|
http:
|
|
routers:
|
|
nucleo-chat-stream:
|
|
rule: "Host(`nucleoriofrio.com`) && Path(`/api/chat/stream`)"
|
|
entryPoints:
|
|
- https
|
|
service: nucleo-z590-alt-svc
|
|
middlewares:
|
|
- no-buffering
|
|
- cors-permissive
|
|
- large-timeout
|
|
tls:
|
|
certResolver: letsencrypt
|
|
priority: 50
|
|
|
|
nucleo-chat-api:
|
|
rule: "Host(`nucleoriofrio.com`) && PathPrefix(`/api/chat/`)"
|
|
entryPoints:
|
|
- https
|
|
service: nucleo-z590-alt-svc
|
|
middlewares:
|
|
- cors-permissive
|
|
tls:
|
|
certResolver: letsencrypt
|
|
priority: 40
|
|
|
|
nucleo-root:
|
|
rule: "Host(`nucleoriofrio.com`)"
|
|
entryPoints:
|
|
- https
|
|
service: nucleo-z590-svc
|
|
middlewares:
|
|
- hsts-headers
|
|
tls:
|
|
certResolver: letsencrypt
|
|
priority: 1
|
|
|
|
services:
|
|
nucleo-z590-svc:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "https://z590.interno.com:3000"
|
|
|
|
nucleo-z590-alt-svc:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "https://192.168.87.135:3000"
|
|
|
|
middlewares:
|
|
cors-permissive:
|
|
headers:
|
|
accessControlAllowOriginList:
|
|
- "*"
|
|
accessControlAllowMethods:
|
|
- "GET"
|
|
- "POST"
|
|
- "OPTIONS"
|
|
accessControlAllowHeaders:
|
|
- "Accept"
|
|
- "Authorization"
|
|
- "Cache-Control"
|
|
- "Content-Type"
|
|
- "DNT"
|
|
- "If-Modified-Since"
|
|
- "Keep-Alive"
|
|
- "Origin"
|
|
- "User-Agent"
|
|
- "X-Requested-With"
|
|
accessControlMaxAge: 100
|
|
addVaryHeader: true
|
|
```
|
|
|
|
---
|
|
|
|
### 3.8 Música Pública (musica.nucleoriofrio.com)
|
|
|
|
**NPM Original**:
|
|
- Multiple locations con diferentes propósitos
|
|
- Caching de assets
|
|
- Security headers
|
|
- Streaming optimizations
|
|
|
|
**Traefik Config** (`/config/musica-public.yml`):
|
|
|
|
```yaml
|
|
http:
|
|
routers:
|
|
musica-pub-nuxt:
|
|
rule: "Host(`musica.nucleoriofrio.com`) && PathPrefix(`/_nuxt/`)"
|
|
entryPoints:
|
|
- https
|
|
service: repodructor-svc
|
|
middlewares:
|
|
- asset-cache-long
|
|
tls:
|
|
certResolver: letsencrypt
|
|
priority: 30
|
|
|
|
musica-pub-music:
|
|
rule: "Host(`musica.nucleoriofrio.com`) && PathPrefix(`/music/`)"
|
|
entryPoints:
|
|
- https
|
|
service: repodructor-svc
|
|
middlewares:
|
|
- no-buffering
|
|
- music-streaming
|
|
- music-cache
|
|
tls:
|
|
certResolver: letsencrypt
|
|
priority: 25
|
|
|
|
musica-pub-api:
|
|
rule: "Host(`musica.nucleoriofrio.com`) && PathPrefix(`/api/`)"
|
|
entryPoints:
|
|
- https
|
|
service: repodructor-svc
|
|
middlewares:
|
|
- api-timeout
|
|
tls:
|
|
certResolver: letsencrypt
|
|
priority: 20
|
|
|
|
musica-pub-root:
|
|
rule: "Host(`musica.nucleoriofrio.com`)"
|
|
entryPoints:
|
|
- https
|
|
service: repodructor-svc
|
|
middlewares:
|
|
- hsts-headers
|
|
- security-headers
|
|
tls:
|
|
certResolver: letsencrypt
|
|
priority: 1
|
|
|
|
services:
|
|
repodructor-svc:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://repodructor:3000"
|
|
|
|
middlewares:
|
|
asset-cache-long:
|
|
headers:
|
|
customResponseHeaders:
|
|
Cache-Control: "public, max-age=31536000, immutable"
|
|
|
|
music-cache:
|
|
headers:
|
|
customResponseHeaders:
|
|
Cache-Control: "public, max-age=3600"
|
|
|
|
music-streaming:
|
|
buffering:
|
|
maxRequestBodyBytes: 0 # Sin límite
|
|
memRequestBodyBytes: 0
|
|
maxResponseBodyBytes: 0
|
|
memResponseBodyBytes: 0
|
|
|
|
api-timeout:
|
|
buffering:
|
|
maxRequestBodyBytes: 10485760 # 10MB
|
|
|
|
security-headers:
|
|
headers:
|
|
customResponseHeaders:
|
|
X-Frame-Options: "SAMEORIGIN"
|
|
X-Content-Type-Options: "nosniff"
|
|
X-XSS-Protection: "1; mode=block"
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Middlewares Comunes
|
|
|
|
### 4.1 Archivo de Middlewares Globales (`/config/middlewares.yml`)
|
|
|
|
```yaml
|
|
http:
|
|
middlewares:
|
|
# HSTS Headers
|
|
hsts-headers:
|
|
headers:
|
|
stsSeconds: 63072000
|
|
stsIncludeSubdomains: true
|
|
stsPreload: true
|
|
forceSTSHeader: true
|
|
|
|
# WebSocket Support
|
|
websocket-headers:
|
|
headers:
|
|
customRequestHeaders:
|
|
X-Forwarded-Proto: "https"
|
|
customResponseHeaders:
|
|
Upgrade: "$http_upgrade"
|
|
Connection: "$http_connection"
|
|
|
|
# Block Common Exploits
|
|
block-exploits:
|
|
headers:
|
|
browserXssFilter: true
|
|
contentTypeNosniff: true
|
|
frameDeny: true
|
|
customFrameOptionsValue: "SAMEORIGIN"
|
|
ipWhiteList:
|
|
sourceRange:
|
|
- "127.0.0.1/32"
|
|
- "192.168.0.0/16"
|
|
- "172.16.0.0/12"
|
|
- "10.0.0.0/8"
|
|
|
|
# Asset Caching
|
|
asset-cache:
|
|
headers:
|
|
customResponseHeaders:
|
|
Cache-Control: "public, max-age=86400"
|
|
|
|
# Security Headers Strict
|
|
security-strict:
|
|
headers:
|
|
sslRedirect: true
|
|
stsSeconds: 31536000
|
|
stsIncludeSubdomains: true
|
|
stsPreload: true
|
|
forceSTSHeader: true
|
|
frameDeny: true
|
|
contentTypeNosniff: true
|
|
browserXssFilter: true
|
|
referrerPolicy: "strict-origin-when-cross-origin"
|
|
permissionsPolicy: "camera=(), microphone=(), geolocation=()"
|
|
customResponseHeaders:
|
|
X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex"
|
|
|
|
# Rate Limiting
|
|
rate-limit:
|
|
rateLimit:
|
|
average: 100
|
|
burst: 200
|
|
period: 1m
|
|
|
|
# Basic Auth (ejemplo)
|
|
auth-basic:
|
|
basicAuth:
|
|
users:
|
|
- "admin:$apr1$..." # htpasswd hash
|
|
removeHeader: true
|
|
|
|
# IP Whitelist (ejemplo para admin panels)
|
|
admin-whitelist:
|
|
ipWhiteList:
|
|
sourceRange:
|
|
- "192.168.87.0/24"
|
|
- "172.19.0.0/16"
|
|
|
|
# Compress
|
|
compress:
|
|
compress: {}
|
|
|
|
# Strip Prefix (útil para algunos backends)
|
|
strip-api-prefix:
|
|
stripPrefix:
|
|
prefixes:
|
|
- "/api"
|
|
forceSlash: false
|
|
|
|
# Redirect Scheme (HTTP → HTTPS)
|
|
redirect-https:
|
|
redirectScheme:
|
|
scheme: https
|
|
permanent: true
|
|
|
|
# Custom Headers for Backend
|
|
backend-headers:
|
|
headers:
|
|
customRequestHeaders:
|
|
X-Forwarded-Proto: "https"
|
|
X-Forwarded-Scheme: "https"
|
|
X-Real-IP: "" # Traefik lo rellena automáticamente
|
|
X-Forwarded-For: ""
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Certificados SSL
|
|
|
|
### 5.1 Certificado Custom (*.interno.com)
|
|
|
|
**Opción A: Usar el certificado autofirmado existente**
|
|
|
|
```yaml
|
|
# En /config/tls.yml
|
|
tls:
|
|
stores:
|
|
default:
|
|
defaultCertificate:
|
|
certFile: /certificates/interno.com/fullchain.pem
|
|
keyFile: /certificates/interno.com/privkey.pem
|
|
|
|
certificates:
|
|
- certFile: /certificates/interno.com/fullchain.pem
|
|
keyFile: /certificates/interno.com/privkey.pem
|
|
stores:
|
|
- default
|
|
```
|
|
|
|
Copiar certificados:
|
|
```bash
|
|
mkdir -p /srv/traefik/certificates/interno.com
|
|
cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/fullchain.pem \
|
|
/srv/traefik/certificates/interno.com/
|
|
cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/privkey.pem \
|
|
/srv/traefik/certificates/interno.com/
|
|
```
|
|
|
|
### 5.2 Let's Encrypt (*.nucleoriofrio.com)
|
|
|
|
Ya configurado en `traefik.yml`:
|
|
|
|
```yaml
|
|
certificatesResolvers:
|
|
letsencrypt:
|
|
acme:
|
|
email: admin@nucleoriofrio.com
|
|
storage: /acme.json
|
|
httpChallenge:
|
|
entryPoint: http
|
|
```
|
|
|
|
Para wildcard con DNS Challenge (Cloudflare):
|
|
|
|
```yaml
|
|
certificatesResolvers:
|
|
letsencrypt:
|
|
acme:
|
|
email: admin@nucleoriofrio.com
|
|
storage: /acme.json
|
|
dnsChallenge:
|
|
provider: cloudflare
|
|
delayBeforeCheck: 10
|
|
resolvers:
|
|
- "1.1.1.1:53"
|
|
- "1.0.0.1:53"
|
|
```
|
|
|
|
Variables de entorno necesarias:
|
|
```bash
|
|
CF_DNS_API_TOKEN=your_cloudflare_token
|
|
CF_ZONE_API_TOKEN=your_zone_token
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Docker Compose Completo
|
|
|
|
### 6.1 Stack Traefik Principal
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
traefik:
|
|
image: traefik:v3.1
|
|
container_name: traefik
|
|
restart: unless-stopped
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
networks:
|
|
- principal
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
- "8080:8080" # Dashboard (cambiar puerto si hay conflicto)
|
|
environment:
|
|
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
|
|
- CF_ZONE_API_TOKEN=${CF_ZONE_API_TOKEN}
|
|
- TZ=America/Argentina/Buenos_Aires
|
|
volumes:
|
|
- /etc/localtime:/etc/localtime:ro
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- ./traefik.yml:/traefik.yml:ro
|
|
- ./config/:/config/:ro
|
|
- ./acme.json:/acme.json
|
|
- ./certificates/:/certificates/:ro
|
|
- ./logs/:/logs/
|
|
labels:
|
|
- "traefik.enable=true"
|
|
|
|
# Dashboard
|
|
- "traefik.http.routers.dashboard.rule=Host(`traefik.interno.com`)"
|
|
- "traefik.http.routers.dashboard.entrypoints=https"
|
|
- "traefik.http.routers.dashboard.tls=true"
|
|
- "traefik.http.routers.dashboard.tls.certresolver=custom"
|
|
- "traefik.http.routers.dashboard.service=api@internal"
|
|
- "traefik.http.routers.dashboard.middlewares=admin-whitelist"
|
|
|
|
networks:
|
|
principal:
|
|
external: true
|
|
```
|
|
|
|
### 6.2 Estructura de Directorios
|
|
|
|
```
|
|
/srv/traefik/
|
|
├── traefik.yml # Config estática
|
|
├── acme.json # Certificados LE (chmod 600)
|
|
├── docker-compose.yml
|
|
├── .env
|
|
├── config/
|
|
│ ├── middlewares.yml # Middlewares globales
|
|
│ ├── middlewares-authentik.yml
|
|
│ ├── tls.yml # Certificados custom
|
|
│ ├── planilla.yml # Config dinámica planilla
|
|
│ ├── musica.yml
|
|
│ ├── snatchgame.yml
|
|
│ ├── nucleoriofrio.yml
|
|
│ └── musica-public.yml
|
|
├── certificates/
|
|
│ └── interno.com/
|
|
│ ├── fullchain.pem
|
|
│ └── privkey.pem
|
|
└── logs/
|
|
├── traefik.log
|
|
└── access.log
|
|
```
|
|
|
|
### 6.3 Preparación
|
|
|
|
```bash
|
|
# Crear estructura
|
|
mkdir -p /srv/traefik/{config,certificates/interno.com,logs}
|
|
|
|
# Crear acme.json
|
|
touch /srv/traefik/acme.json
|
|
chmod 600 /srv/traefik/acme.json
|
|
|
|
# Copiar certificados
|
|
cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/fullchain.pem \
|
|
/srv/traefik/certificates/interno.com/
|
|
cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/privkey.pem \
|
|
/srv/traefik/certificates/interno.com/
|
|
|
|
# Crear archivos de config
|
|
touch /srv/traefik/traefik.yml
|
|
touch /srv/traefik/config/{middlewares,tls,planilla,musica,snatchgame}.yml
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Scripts de Migración
|
|
|
|
### 7.1 Script de Pre-Migración
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# pre-migration.sh
|
|
|
|
set -e
|
|
|
|
echo "🔍 Pre-Migration Checklist para Traefik"
|
|
|
|
# Backup de NPM
|
|
echo "📦 Backing up NPM configs..."
|
|
mkdir -p /srv/backups/npm-$(date +%Y%m%d)
|
|
cp -r /srv/nginx-proxy-manager/data/nginx /srv/backups/npm-$(date +%Y%m%d)/
|
|
cp -r /srv/nginx-proxy-manager/data/custom_ssl /srv/backups/npm-$(date +%Y%m%d)/
|
|
|
|
# Verificar red principal
|
|
echo "🌐 Verificando red principal..."
|
|
docker network inspect principal > /dev/null 2>&1 || {
|
|
echo "❌ Red 'principal' no existe"
|
|
exit 1
|
|
}
|
|
|
|
# Verificar contenedores backend
|
|
echo "🔍 Verificando backends..."
|
|
backends=(
|
|
"portainer"
|
|
"gitea"
|
|
"geo-ids"
|
|
"planilla-ui"
|
|
"planilla-api"
|
|
"planilla-mcp"
|
|
"whatsapp-router"
|
|
"repodructor"
|
|
"amigos-app"
|
|
"duplicati"
|
|
"radiusnucleo-node-1"
|
|
)
|
|
|
|
for backend in "${backends[@]}"; do
|
|
docker ps --filter "name=$backend" --format "{{.Names}}" | grep -q "$backend" || {
|
|
echo "⚠️ Backend '$backend' no encontrado o detenido"
|
|
}
|
|
done
|
|
|
|
# Crear estructura Traefik
|
|
echo "📁 Creando estructura de directorios..."
|
|
mkdir -p /srv/traefik/{config,certificates/interno.com,logs}
|
|
touch /srv/traefik/acme.json
|
|
chmod 600 /srv/traefik/acme.json
|
|
|
|
# Copiar certificados
|
|
echo "🔐 Copiando certificados SSL..."
|
|
cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/fullchain.pem \
|
|
/srv/traefik/certificates/interno.com/
|
|
cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/privkey.pem \
|
|
/srv/traefik/certificates/interno.com/
|
|
|
|
echo "✅ Pre-migration completa"
|
|
echo ""
|
|
echo "Próximos pasos:"
|
|
echo "1. Crear archivos de configuración de Traefik"
|
|
echo "2. Actualizar labels en docker-compose de cada stack"
|
|
echo "3. Ejecutar migration.sh"
|
|
```
|
|
|
|
### 7.2 Script de Migración
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# migration.sh
|
|
|
|
set -e
|
|
|
|
echo "🚀 Iniciando migración NPM → Traefik"
|
|
|
|
# Detener NPM
|
|
echo "🛑 Deteniendo NPM..."
|
|
cd /srv/nginx-proxy-manager
|
|
docker-compose down
|
|
|
|
# Levantar Traefik
|
|
echo "🚀 Levantando Traefik..."
|
|
cd /srv/traefik
|
|
docker-compose up -d
|
|
|
|
# Esperar a que Traefik esté listo
|
|
echo "⏳ Esperando a que Traefik esté listo..."
|
|
sleep 10
|
|
|
|
# Verificar Traefik
|
|
echo "✅ Verificando Traefik..."
|
|
docker logs traefik --tail 50
|
|
|
|
# Verificar dashboard
|
|
echo "🌐 Dashboard disponible en: https://traefik.interno.com"
|
|
|
|
# Restart de cada stack para aplicar labels
|
|
echo "🔄 Reiniciando stacks con nuevas labels..."
|
|
stacks=(
|
|
"/srv/gitea"
|
|
"/srv/planilla"
|
|
"/srv/conversation-layer"
|
|
# ... más stacks
|
|
)
|
|
|
|
for stack in "${stacks[@]}"; do
|
|
if [ -d "$stack" ]; then
|
|
echo " Reiniciando $stack..."
|
|
cd "$stack"
|
|
docker-compose up -d --force-recreate
|
|
fi
|
|
done
|
|
|
|
echo "✅ Migración completa!"
|
|
echo ""
|
|
echo "Verificar:"
|
|
echo "1. Dashboard Traefik: https://traefik.interno.com"
|
|
echo "2. Logs: docker logs traefik -f"
|
|
echo "3. Probar cada dominio manualmente"
|
|
```
|
|
|
|
### 7.3 Script de Rollback
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# rollback.sh
|
|
|
|
set -e
|
|
|
|
echo "⏪ Rollback a NPM"
|
|
|
|
# Detener Traefik
|
|
echo "🛑 Deteniendo Traefik..."
|
|
cd /srv/traefik
|
|
docker-compose down
|
|
|
|
# Levantar NPM
|
|
echo "🚀 Levantando NPM..."
|
|
cd /srv/nginx-proxy-manager
|
|
docker-compose up -d
|
|
|
|
echo "✅ Rollback completo"
|
|
echo "NPM disponible en: http://Nginx.interno.com:81"
|
|
```
|
|
|
|
### 7.4 Script de Verificación
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# verify-migration.sh
|
|
|
|
domains=(
|
|
"portainer.interno.com"
|
|
"gitea.interno.com"
|
|
"geoids.interno.com"
|
|
"planilla.interno.com"
|
|
"musica.interno.com"
|
|
"nucleoriofrio.com"
|
|
"snatchgame.interno.com"
|
|
"amigos.nucleoriofrio.com"
|
|
)
|
|
|
|
echo "🔍 Verificando dominios..."
|
|
|
|
for domain in "${domains[@]}"; do
|
|
echo -n "Testing $domain... "
|
|
if curl -skI "https://$domain" | head -1 | grep -q "200\|301\|302"; then
|
|
echo "✅ OK"
|
|
else
|
|
echo "❌ FAIL"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
echo "Verificar manualmente:"
|
|
echo "1. Autenticación Authentik en geoids.interno.com"
|
|
echo "2. WebSocket en musica.interno.com"
|
|
echo "3. Multi-location en planilla.interno.com"
|
|
echo "4. API streaming en nucleoriofrio.com/api/chat/stream"
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Tabla de Mapeo Completa
|
|
|
|
### NPM → Traefik Equivalencias
|
|
|
|
| Característica NPM | Equivalente Traefik |
|
|
|-------------------|---------------------|
|
|
| `proxy_pass` | `service.loadBalancer.server.url` |
|
|
| `server_name` | `router.rule=Host(...)` |
|
|
| `location /path` | `router.rule=PathPrefix(...)` + `priority` |
|
|
| `proxy_set_header` | `middleware.headers.customRequestHeaders` |
|
|
| `add_header` | `middleware.headers.customResponseHeaders` |
|
|
| `auth_request` | `middleware.forwardAuth` |
|
|
| `ssl_certificate` | `tls.certificates` o `tls.certResolver` |
|
|
| `include force-ssl.conf` | `entrypoint.http.redirections` |
|
|
| `proxy_buffering off` | `middleware.buffering` |
|
|
| `client_max_body_size` | `middleware.buffering.maxRequestBodyBytes` |
|
|
| `proxy_read_timeout` | `service.loadBalancer.responseForwarding.timeout` |
|
|
| `upstream` con múltiples IPs | `service.loadBalancer.servers` (array) |
|
|
| `map` para variables | Middlewares condicionales o dynamic config |
|
|
| `if` statements | No soportado directamente, usar priorities |
|
|
| `limit_req` | `middleware.rateLimit` |
|
|
| `access_log` | `accessLog` en traefik.yml |
|
|
| `error_log` | `log` en traefik.yml |
|
|
|
|
---
|
|
|
|
## 9. Checklist de Migración
|
|
|
|
### Antes de Migrar
|
|
|
|
- [ ] Backup completo de NPM configs
|
|
- [ ] Backup de base de datos de NPM
|
|
- [ ] Copiar certificados SSL
|
|
- [ ] Verificar que todos los backends están en red `principal`
|
|
- [ ] Documentar configuraciones custom
|
|
|
|
### Durante la Migración
|
|
|
|
- [ ] Crear estructura de directorios de Traefik
|
|
- [ ] Configurar `traefik.yml`
|
|
- [ ] Crear middlewares comunes
|
|
- [ ] Crear configs dinámicas por servicio
|
|
- [ ] Actualizar docker-compose de cada stack con labels
|
|
- [ ] Detener NPM
|
|
- [ ] Levantar Traefik
|
|
- [ ] Verificar dashboard de Traefik
|
|
|
|
### Después de Migrar
|
|
|
|
- [ ] Probar cada dominio manualmente
|
|
- [ ] Verificar certificados SSL
|
|
- [ ] Verificar autenticación Authentik
|
|
- [ ] Verificar WebSockets
|
|
- [ ] Verificar multi-location routing
|
|
- [ ] Verificar streaming endpoints
|
|
- [ ] Monitorear logs de Traefik
|
|
- [ ] Configurar alertas (opcional)
|
|
- [ ] Actualizar documentación
|
|
|
|
### En Caso de Problemas
|
|
|
|
- [ ] Rollback a NPM
|
|
- [ ] Revisar logs de Traefik
|
|
- [ ] Verificar conectividad de red
|
|
- [ ] Verificar resolución DNS interna
|
|
- [ ] Verificar permisos de certificados
|
|
|