Configuración de DNS-over-HTTPS y DNS-over-TLS

DNS-over-HTTPS (DoH) y DNS-over-TLS (DoT) son protocolos que cifran las consultas DNS para proteger la privacidad del usuario y evitar la interceptación y manipulación de respuestas DNS por parte de terceros o proveedores de Internet. Mientras que DoT usa el puerto dedicado 853, DoH encapsula las consultas en HTTPS en el puerto 443, lo que dificulta su bloqueo. Esta guía cubre la configuración de servidores DoH y DoT con Nginx en Linux, así como la configuración de clientes.

Requisitos Previos

  • Servidor Linux (Ubuntu 22.04/Debian 12 o CentOS 9/Rocky 9)
  • Dominio público con certificado TLS válido (Let's Encrypt funciona)
  • Nginx instalado
  • Servidor DNS local (Unbound, BIND o similar) en el servidor
  • Puerto 443 (DoH) y/o 853 (DoT) accesibles desde Internet
  • Certbot instalado para gestión de certificados

Arquitectura de DNS Cifrado

El flujo típico de consulta cifrada funciona así:

Cliente (navegador/sistema)
  → DoH (HTTPS:443) o DoT (TLS:853)
    → Proxy/Terminador TLS (Nginx/Stunnel)
      → Servidor DNS local (Unbound/127.0.0.1:53)
        → Internet (resolución recursiva)

Esta arquitectura permite que el servidor Linux actúe como gateway DNS cifrado para toda la red.

Configuración del Servidor DoT con Stunnel

Stunnel actúa como wrapper TLS sobre el servidor DNS local en el puerto 853:

# Instalar Stunnel
apt-get install -y stunnel4  # Ubuntu/Debian
# dnf install -y stunnel     # CentOS/Rocky

# Obtener certificado TLS con Let's Encrypt
apt-get install -y certbot
certbot certonly --standalone \
  -d dns.tudominio.com \
  --email [email protected] \
  --agree-tos --non-interactive

# Crear configuración de Stunnel para DoT
cat > /etc/stunnel/dns-over-tls.conf << 'EOF'
# Configuración global
pid = /run/stunnel4/stunnel.pid
setuid = stunnel4
setgid = stunnel4
socket = l:TCP_NODELAY=1
socket = r:TCP_NODELAY=1

# Nivel de log (0=emergency, 7=debug)
debug = 4
output = /var/log/stunnel4/stunnel.log

# Servicio DNS-over-TLS en el puerto 853
[dot-server]
accept  = 0.0.0.0:853
connect = 127.0.0.1:53

# Certificados TLS
cert = /etc/letsencrypt/live/dns.tudominio.com/fullchain.pem
key  = /etc/letsencrypt/live/dns.tudominio.com/privkey.pem

# Protocolos TLS permitidos
sslVersion = TLSv1.2
ciphers = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305

# Compatibilidad con clientes que usan ALPN
options = NO_SSLv3
options = NO_TLSv1
options = NO_TLSv1.1
EOF

# Habilitar e iniciar Stunnel
systemctl enable --now stunnel4

# Verificar que el puerto 853 está escuchando
ss -tlnp | grep 853

Servidor DoH con Nginx y dns-over-https

Instala el proxy DoH de m13253/dns-over-https:

# Instalar Go (necesario para compilar)
apt-get install -y golang-go git

# Clonar y compilar dns-over-https
git clone https://github.com/m13253/dns-over-https.git /opt/dns-over-https
cd /opt/dns-over-https
make

# Instalar los binarios
make install

# Configurar el servidor DoH
cat > /etc/dns-over-https/doh-server.conf << 'EOF'
listen = ["127.0.0.1:8053"]

# Backend DNS (servidor local Unbound)
local_addr = "127.0.0.1:53"

# Timeout para consultas DNS
timeout = 10

# Parámetros de compresión
compress = false

# Verbose logging (false en producción)
verbose = false

# Certificado (manejado por Nginx como terminador TLS)
# cert = ""
# key  = ""
EOF

# Crear servicio systemd para doh-server
cat > /etc/systemd/system/doh-server.service << 'EOF'
[Unit]
Description=DNS-over-HTTPS Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=nobody
Group=nogroup
ExecStart=/usr/local/bin/doh-server -conf /etc/dns-over-https/doh-server.conf
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now doh-server

Proxy DoH con Nginx

Nginx actúa como terminador TLS y enruta el tráfico DoH al servidor backend:

# Instalar Nginx si no está disponible
apt-get install -y nginx

# Configuración de Nginx para DoH
cat > /etc/nginx/sites-available/doh << 'EOF'
# Redirigir HTTP a HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name dns.tudominio.com;
    return 301 https://$host$request_uri;
}

# Servidor DoH principal
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name dns.tudominio.com;

    # Certificados TLS (Let's Encrypt)
    ssl_certificate     /etc/letsencrypt/live/dns.tudominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/dns.tudominio.com/privkey.pem;

    # Parámetros TLS modernos
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_stapling on;
    ssl_stapling_verify on;

    # Cabeceras de seguridad
    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    # Endpoint de DoH
    location /dns-query {
        # Reenviar al servidor doh-server backend
        proxy_pass http://127.0.0.1:8053/dns-query;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # Cabeceras necesarias para DoH
        proxy_set_header Accept "application/dns-message";
        
        # Timeouts para consultas DNS
        proxy_read_timeout 10s;
        proxy_connect_timeout 5s;
        
        # Soporte para DNS wireformat (POST y GET)
        proxy_http_version 1.1;
    }

    # Página de información del servidor (opcional)
    location = / {
        return 200 "DNS-over-HTTPS server operativo en dns.tudominio.com";
        add_header Content-Type text/plain;
    }
}
EOF

# Activar la configuración
ln -s /etc/nginx/sites-available/doh /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Integración con Unbound

Si usas Unbound como resolvedor DNS local, asegúrate de que acepta consultas del proxy:

# En /etc/unbound/unbound.conf, verificar:
# server:
#     interface: 127.0.0.1
#     interface: 0.0.0.0  # Si el proxy está en otro servidor
#     access-control: 127.0.0.0/8 allow

# Reiniciar Unbound para aplicar cambios
systemctl restart unbound

# Verificar que Unbound responde correctamente al proxy
dig @127.0.0.1 google.com

# Probar el endpoint DoH directamente
curl -s "https://dns.tudominio.com/dns-query?name=google.com&type=A" \
  -H "Accept: application/dns-json" | python3 -m json.tool

Configuración de Clientes

# Linux (systemd-resolved con DoT)
cat > /etc/systemd/resolved.conf.d/dot.conf << 'EOF'
[Resolve]
DNS=dns.tudominio.com
DNSOverTLS=yes
DNSSEC=yes
EOF
systemctl restart systemd-resolved

# Verificar que se usa DoT
resolvectl status
resolvectl query google.com

# Linux (usando DoH con stubby como cliente local)
apt-get install -y stubby
cat > /etc/stubby/stubby.yml << 'EOF'
resolution_type: GETDNS_RESOLUTION_STUB
dns_transport_list:
  - GETDNS_TRANSPORT_TLS
tls_authentication: GETDNS_AUTHENTICATION_REQUIRED
round_robin_upstreams: 1
listen_addresses:
  - 127.0.0.1@5353
upstream_recursive_servers:
  - address_data: dns.tudominio.com
    tls_auth_name: "dns.tudominio.com"
    tls_port: 853
EOF
systemctl enable --now stubby

# Firefox - DoH nativo
# Ajustes > General > Configuración de red > Ajustes
# Activar "Habilitar DNS sobre HTTPS"
# URL personalizada: https://dns.tudominio.com/dns-query

# Chrome/Chromium - DoH nativo
# chrome://settings/security > Usar DNS seguro
# Personalizar con: https://dns.tudominio.com/dns-query

Solución de Problemas

# Verificar que el puerto 853 (DoT) está abierto
nc -zv dns.tudominio.com 853
openssl s_client -connect dns.tudominio.com:853 -servername dns.tudominio.com

# Probar DoT manualmente con kdig
apt-get install -y knot-dnsutils
kdig @dns.tudominio.com +tls-ca google.com A

# Verificar DoH
curl -v "https://dns.tudominio.com/dns-query?name=google.com&type=A" \
  -H "Accept: application/dns-json"

# Ver logs de Nginx
tail -f /var/log/nginx/access.log /var/log/nginx/error.log

# Ver logs del servidor DoH
journalctl -u doh-server -f

# Ver logs de Stunnel (para DoT)
tail -f /var/log/stunnel4/stunnel.log

# Renovación automática del certificado Let's Encrypt
# Añadir hook post-renovación para recargar servicios
cat > /etc/letsencrypt/renewal-hooks/post/reload-dns.sh << 'EOF'
#!/bin/bash
systemctl reload nginx
systemctl restart stunnel4
EOF
chmod +x /etc/letsencrypt/renewal-hooks/post/reload-dns.sh

Conclusión

La implementación de DNS-over-HTTPS y DNS-over-TLS en tu propia infraestructura Linux proporciona un control total sobre la privacidad de las consultas DNS de tu red, eliminando la dependencia de proveedores externos y garantizando que las consultas DNS no sean interceptadas ni registradas por terceros. La combinación de Nginx como terminador TLS y Unbound como resolvedor recursivo ofrece una solución completa, escalable y mantenible para cifrado DNS en producción.