Exploring the roots and future of Internet Relay Chat

IRC detrás de Nginx: proxy inverso, WebSocket y TLS termination

En 2017, tenía un servidor IRC expuesto directamente a internet. Un martes, un ataque DDoS a las 6 de la mañana saturó la IP y dejó fuera no solo el IRC sino también el servidor web y el email que compartían la misma máquina. Ese mismo día monté un proxy inverso con Nginx. El ircd dejó de estar expuesto. Los certificados TLS se gestionaban en un solo punto. Y cuando vino el siguiente ataque, Nginx absorbió el impacto sin que el ircd se enterara. Desde entonces, ninguno de mis servidores IRC toca internet directamente.

Por qué poner un proxy delante de tu ircd

Hay cinco razones técnicas concretas.

Ocultación de IP. Si el ircd está detrás del proxy, su IP real no está expuesta. Los ataques DDoS van contra la IP del proxy, que puede estar en una red con mitigación DDoS (Cloudflare Spectrum, OVH Anti-DDoS, etc.) mientras el ircd está en una máquina con IP privada.

TLS termination centralizada. Los certificados Let's Encrypt se gestionan en el proxy. El ircd recibe tráfico sin cifrar internamente (o con un certificado autofirmado). Renovar certificados no requiere recargar el ircd. Cambiar de Let's Encrypt a otro proveedor no afecta al ircd.

WebSocket gateway. Los clientes IRC web (como Kiwi IRC, The Lounge, Gamja) necesitan WebSocket. No todos los ircds soportan WebSocket nativamente. Nginx puede aceptar WebSocket y pasarlo al ircd como TCP plano, o al revés.

Load balancing. Si tienes múltiples servidores IRC enlazados, el proxy puede distribuir conexiones. Nuevos usuarios van al servidor con menos carga. Si un servidor cae, el proxy redirige a los demás.

Logging y métricas. El proxy puede registrar conexiones, throughput, errores, sin modificar el ircd. Integración con Prometheus, Grafana, o cualquier sistema de monitorización.

Empresas de servicios informáticos como PowerITi implementan estas arquitecturas con proxy inverso para sus clientes PYME, separando la capa de exposición pública de los servicios internos. Es un patrón estándar de infraestructura que aplica a cualquier servicio TCP, no solo IRC.

Nginx stream: proxy TCP para IRC nativo

IRC es un protocolo TCP. Para proxear tráfico TCP puro, Nginx tiene el módulo stream. No es el http que usas para sitios web — es un módulo diferente diseñado para tráfico TCP y UDP arbitrario.

# /etc/nginx/nginx.conf
# El bloque stream va FUERA del bloque http

stream {
    # Logging (opcional pero recomendado)
    log_format irc '$remote_addr [$time_local] '
                   '$protocol $status $bytes_sent $bytes_received '
                   '$session_time "$upstream_addr"';

    access_log /var/log/nginx/irc-access.log irc;

    # Proxy TCP para IRC sin TLS (puerto 6667)
    server {
        listen 6667;
        proxy_pass 127.0.0.1:16667;     # ircd escucha en puerto local
        proxy_timeout 300s;
        proxy_connect_timeout 10s;
    }

    # Proxy TCP para IRC con TLS (puerto 6697)
    server {
        listen 6697 ssl;
        ssl_certificate /etc/letsencrypt/live/irc.tudominio.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/irc.tudominio.com/privkey.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_session_cache shared:IRC:10m;
        ssl_session_timeout 1h;

        proxy_pass 127.0.0.1:16667;     # Al ircd SIN TLS internamente
        proxy_timeout 300s;
        proxy_connect_timeout 10s;

        # PROXY protocol para pasar IP real al ircd
        proxy_protocol on;
    }
}

# NOTA: el módulo stream debe estar habilitado
# En Debian/Ubuntu: apt install libnginx-mod-stream
# En RHEL/CentOS: incluido en nginx-mod-stream

El punto clave es proxy_protocol on. Sin esto, el ircd ve todas las conexiones viniendo de 127.0.0.1. Con PROXY protocol, Nginx envía la IP real del cliente al ircd. El ircd necesita soportar PROXY protocol — UnrealIRCd, InspIRCd y Ergo lo soportan.

# Configurar UnrealIRCd para aceptar PROXY protocol
# unrealircd.conf

listen {
    ip 127.0.0.1;
    port 16667;
    options {
        proxy-protocol;
    }
}

# Configurar Ergo para PROXY protocol
# ircd.yaml
server:
    listeners:
        "127.0.0.1:16667":
            proxy: true

# Configurar InspIRCd para PROXY protocol
# inspircd.conf
<module name="haproxy">
<bind address="127.0.0.1" port="16667" type="clients" hook="haproxy">
Advertencia: Si activas PROXY protocol en el ircd, SOLO el proxy debe poder conectar a ese puerto. Si un usuario conecta directamente al puerto con PROXY protocol habilitado, puede falsificar su IP enviando una cabecera PROXY protocol falsa. Asegúrate de que el firewall bloquea el acceso directo al puerto del ircd desde cualquier IP que no sea el proxy.

Nginx como proxy WebSocket para IRC web

Los clientes IRC web usan WebSocket. Nginx puede proxear WebSocket al ircd si el ircd soporta WebSocket nativo, o puedes usar herramientas intermedias como websockify para convertir WebSocket a TCP.

# /etc/nginx/sites-enabled/irc-websocket.conf

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 443 ssl http2;
    server_name irc.tudominio.com;

    ssl_certificate /etc/letsencrypt/live/irc.tudominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/irc.tudominio.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # WebSocket endpoint para clientes IRC web
    location /webirc {
        proxy_pass http://127.0.0.1:8067;   # Puerto WebSocket del ircd
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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;

        # Timeouts largos para conexiones IRC persistentes
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;

        # Sin buffering para tiempo real
        proxy_buffering off;
    }

    # Página de aterrizaje para clientes web
    location / {
        root /var/www/irc;
        index index.html;
    }
}

La directiva map al inicio es necesaria para que Nginx maneje correctamente el upgrade de HTTP a WebSocket. Sin ella, Nginx responde con 400 Bad Request cuando el cliente intenta el handshake WebSocket. Es un error que todo el mundo comete la primera vez.

proxy_read_timeout 300s es necesario porque las conexiones IRC son persistentes. El valor por defecto de Nginx (60s) cerraría conexiones inactivas demasiado pronto. 300 segundos es un buen equilibrio — si no hay actividad en 5 minutos, el proxy cierra la conexión. El cliente debería reconectar automáticamente.

TLS termination: centralizar certificados

Con TLS termination en el proxy, el flujo es: Cliente ---TLS---> Nginx ---plain---> ircd. Nginx maneja todo el TLS. El ircd recibe texto plano.

Las ventajas son prácticas. Renovar certificados no requiere tocar el ircd. Puedes usar el mismo certificado para IRC, WebSocket y web. Certbot se integra nativamente con Nginx. Y si necesitas cambiar la configuración TLS (añadir un cipher, cambiar la versión mínima), lo haces en un solo lugar.

# Renovación automática con Certbot + Nginx
# Certbot detecta la configuración de Nginx automáticamente
sudo certbot --nginx -d irc.tudominio.com

# Verificar renovación automática
sudo certbot renew --dry-run

# Cron de renovación (Certbot lo instala automáticamente)
# 0 0,12 * * * root certbot renew --quiet --deploy-hook "nginx -s reload"

# Configuración TLS recomendada (en nginx.conf o snippet)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=63072000" always;

OCSP stapling (ssl_stapling on) es un detalle que muchos ignoran pero mejora el tiempo de conexión. Sin OCSP stapling, el cliente tiene que hacer una petición adicional al CA para verificar que el certificado no está revocado. Con stapling, Nginx incluye esa verificación en el handshake TLS. Para conexiones IRC donde la latencia importa, cada milisegundo cuenta.

Consejo: Si tu ircd soporta TLS de forma nativa y no necesitas WebSocket ni load balancing, un proxy puede ser innecesario. La complejidad adicional del proxy no siempre se justifica. Para un servidor IRC personal con 50 usuarios, el ircd directo con Let's Encrypt es suficiente. El proxy tiene sentido cuando necesitas separar la IP pública del ircd, manejar múltiples protocolos, o distribuir carga.

HAProxy: cuando Nginx no alcanza

Nginx es excelente para HTTP y WebSocket. Para proxeo TCP puro con funcionalidades avanzadas (health checks TCP, stick tables, ACLs por contenido TCP), HAProxy es superior.

# /etc/haproxy/haproxy.cfg

global
    log /dev/log local0
    maxconn 4096
    user haproxy
    group haproxy
    daemon
    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
    ssl-default-bind-options ssl-min-ver TLSv1.2
    tune.ssl.default-dh-param 2048

defaults
    log global
    mode tcp
    timeout connect 10s
    timeout client 300s
    timeout server 300s
    option tcplog

# Frontend: lo que ven los clientes
frontend irc_tls
    bind *:6697 ssl crt /etc/haproxy/certs/irc.tudominio.com.pem
    default_backend irc_servers

frontend irc_plain
    bind *:6667
    default_backend irc_servers

# Backend: servidores IRC reales
backend irc_servers
    balance roundrobin
    option tcp-check

    # Health check: enviar PING y esperar respuesta
    tcp-check connect
    tcp-check send "PING :haproxy\r\n"
    tcp-check expect string PONG

    # PROXY protocol para pasar IP real
    server irc1 10.0.1.10:6667 check send-proxy-v2 inter 30s
    server irc2 10.0.1.11:6667 check send-proxy-v2 inter 30s backup

El health check de HAProxy es más sofisticado que el de Nginx. No solo verifica que el puerto esté abierto — envía un PING IRC real y espera un PONG. Si el ircd está colgado pero el puerto sigue abierto (cosa que pasa), HAProxy detecta que no responde y deja de enviarle tráfico. Nginx solo puede verificar conectividad TCP.

send-proxy-v2 envía PROXY protocol v2 (binario, más eficiente que v1 texto). HAProxy fue quien inventó PROXY protocol, así que su implementación es la referencia.

Load balancing multi-servidor

Si tienes una red IRC con múltiples servidores enlazados, el proxy puede distribuir conexiones nuevas al servidor con menos carga.

# HAProxy con balanceo inteligente
backend irc_servers
    balance leastconn          # Enviar al servidor con menos conexiones
    option tcp-check
    tcp-check connect
    tcp-check send "PING :lb\r\n"
    tcp-check expect string PONG

    # Sticky sessions por IP: un usuario siempre va al mismo servidor
    # (evita problemas de reconexión a distinto servidor)
    stick-table type ip size 100k expire 30m
    stick on src

    server irc-eu1 10.0.1.10:6667 check send-proxy-v2 weight 100
    server irc-eu2 10.0.1.11:6667 check send-proxy-v2 weight 100
    server irc-us1 10.0.2.10:6667 check send-proxy-v2 weight 50   # Menos peso

# Nginx stream equivalente (más simple, menos funcionalidades)
stream {
    upstream irc_backend {
        least_conn;
        server 10.0.1.10:6667 weight=5;
        server 10.0.1.11:6667 weight=5;
        server 10.0.2.10:6667 weight=2;
    }

    server {
        listen 6697 ssl;
        ssl_certificate /etc/letsencrypt/live/irc.tudominio.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/irc.tudominio.com/privkey.pem;
        proxy_pass irc_backend;
        proxy_protocol on;
    }
}

La sticky table de HAProxy es interesante. Guarda qué IP fue a qué servidor. Si un usuario se desconecta y reconecta en los siguientes 30 minutos, va al mismo servidor. Esto es útil porque en IRC, reconectar al mismo servidor significa que tus canales, tu identificación NickServ, y tu estado se restauran más rápido que si caes en un servidor diferente de la red.

Hardening y seguridad

El proxy es tu primera línea de defensa. Configúralo como tal.

# Firewall: solo el proxy accede al ircd
# iptables (en el servidor del ircd)
iptables -A INPUT -p tcp --dport 16667 -s 10.0.0.1 -j ACCEPT  # IP del proxy
iptables -A INPUT -p tcp --dport 16667 -j DROP                 # Todo lo demás

# nftables equivalente
nft add rule inet filter input tcp dport 16667 ip saddr 10.0.0.1 accept
nft add rule inet filter input tcp dport 16667 drop

# Rate limiting en Nginx stream
stream {
    # Limitar conexiones nuevas por IP
    limit_conn_zone $binary_remote_addr zone=irc_conn:10m;

    server {
        listen 6697 ssl;
        # Max 3 conexiones simultáneas por IP
        limit_conn irc_conn 3;
        proxy_pass 127.0.0.1:16667;
    }
}

# Rate limiting en HAProxy
frontend irc_tls
    bind *:6697 ssl crt /etc/haproxy/certs/irc.pem

    # Stick table para rate limiting
    stick-table type ip size 100k expire 30s store conn_rate(10s)

    # Rechazar IPs con más de 10 conexiones en 10 segundos
    acl too_fast sc0_conn_rate gt 10
    tcp-request connection track-sc0 src
    tcp-request connection reject if too_fast

    default_backend irc_servers

El rate limiting en el proxy es la primera capa. El ircd tiene su propio anti-flood como segunda capa. Y el firewall del sistema operativo como tercera. Tres capas independientes. Si una falla, las otras dos siguen protegiendo.

Nunca expongas el puerto del ircd directamente. Nunca confíes en que el proxy siempre estará ahí. Configura el ircd como si estuviera expuesto directamente, y luego añade el proxy como capa adicional. Defensa en profundidad no es un concepto abstracto — es la diferencia entre un servidor que aguanta un ataque y uno que no.

Preguntas frecuentes

¿Nginx o HAProxy para mi servidor IRC?

Si solo necesitas TLS termination y WebSocket proxy, Nginx es suficiente y probablemente ya lo tienes instalado para tu web. Si necesitas health checks a nivel de protocolo IRC, balanceo avanzado, stick tables, o manejas miles de conexiones simultáneas, HAProxy es la mejor opción. Ambos se pueden combinar: HAProxy para TCP IRC y Nginx para WebSocket y la web.

¿PROXY protocol afecta al rendimiento?

El overhead es insignificante. PROXY protocol v2 añade 16 bytes por conexión. No por mensaje — por conexión. En una conexión IRC que dura horas y transfiere megabytes de texto, 16 bytes adicionales al inicio son irrelevantes. No hay impacto medible en latencia ni en throughput.

¿Puedo usar Cloudflare para proteger mi servidor IRC?

Cloudflare ofrece Spectrum, que puede proxear tráfico TCP arbitrario incluyendo IRC. Sin embargo, Spectrum es un producto de pago (plan Enterprise o como add-on). Cloudflare gratuito solo proxea HTTP/HTTPS, no TCP genérico. Si tu presupuesto lo permite, Spectrum es excelente para mitigación DDoS de IRC. Si no, OVH Anti-DDoS (incluido con sus VPS) es una alternativa gratuita razonable.