Todos los artículos

HTTPS, CORS y headers de seguridad: lo básico que todo backend debe tener

Una guía práctica de seguridad web para desarrolladores backend: cómo configurar HTTPS correctamente, qué es CORS y cómo no romper tu API, y qué headers de seguridad agregar hoy mismo.

HTTPS, CORS y headers de seguridad: lo básico que todo backend debe tener

La seguridad en aplicaciones web no requiere ser un experto en ciberseguridad para implementar correctamente lo básico. La mayoría de los ataques comunes se previenen con tres cosas bien configuradas: HTTPS, CORS y los headers HTTP adecuados. Esta guía es práctica y directa al grano.

HTTPS: no es opcional

En 2026, servir cualquier aplicación sobre HTTP puro es inaceptable. HTTPS cifra la comunicación entre el cliente y el servidor, impidiendo que terceros intercepten o modifiquen los datos en tránsito.

Cómo obtener un certificado gratis con Let’s Encrypt

Si usas Nginx o Apache directamente en un VPS, Certbot automatiza la obtención y renovación:

# Ubuntu / Debian con Nginx
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d tudominio.com -d www.tudominio.com

# Renovación automática (ya la configura certbot, pero puedes verificar)
sudo systemctl status certbot.timer

Si usas Docker + Nginx, la opción más sencilla es Traefik como proxy reverso — detecta servicios automáticamente y gestiona certificados via Let’s Encrypt:

# docker-compose.yml simplificado con Traefik
services:
  traefik:
    image: traefik:v3
    command:
      - "--certificatesresolvers.myresolver.acme.email=tu@email.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "letsencrypt:/letsencrypt"

  mi-app:
    image: mi-app:latest
    labels:
      - "traefik.http.routers.mi-app.rule=Host(`tudominio.com`)"
      - "traefik.http.routers.mi-app.tls.certresolver=myresolver"

Redirigir HTTP a HTTPS siempre

En Nginx:

server {
  listen 80;
  server_name tudominio.com;
  return 301 https://$host$request_uri;
}

CORS: la fuente de confusión número uno

CORS (Cross-Origin Resource Sharing) es el mecanismo que controla qué orígenes externos pueden hacer peticiones a tu API. El error más común es configurarlo demasiado permisivo (Access-Control-Allow-Origin: *) en producción, o configurarlo mal y bloquear tu propio frontend.

Cómo funciona en 60 segundos

Tu frontend en https://app.tudominio.com hace una petición fetch a https://api.tudominio.com. El navegador envía el header Origin: https://app.tudominio.com. Tu API debe responder con Access-Control-Allow-Origin: https://app.tudominio.com para que el navegador permita la respuesta.

Para requests con credenciales (cookies, Authorization headers), debes:

  1. Especificar el origen exacto (no *)
  2. Agregar Access-Control-Allow-Credentials: true
  3. En el frontend, usar credentials: 'include'

Configuración correcta en Node.js con Express

import cors from 'cors';

const allowedOrigins = [
  'https://app.tudominio.com',
  'https://tudominio.com',
  // Solo en desarrollo:
  ...(process.env.NODE_ENV === 'development' ? ['http://localhost:5173'] : []),
];

app.use(cors({
  origin: (origin, callback) => {
    // Permitir requests sin origin (Postman, curl, server-to-server)
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error(`Origen no permitido: ${origin}`));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));

El error de preflight

Las peticiones con métodos distintos a GET/POST o con headers personalizados (como Authorization) primero disparan una petición OPTIONS (preflight). Tu servidor debe responder a esa petición con los headers CORS correctos y status 200/204, o el navegador bloqueará la petición real:

// Express ya lo maneja si configuras cors() antes de las rutas
// Pero si usas Nginx como proxy, agrega esto:
location /api/ {
  if ($request_method = OPTIONS) {
    add_header Access-Control-Allow-Origin $http_origin always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
    add_header Access-Control-Allow-Credentials "true" always;
    add_header Content-Length 0;
    return 204;
  }
  proxy_pass http://backend:3000/;
}

Headers de seguridad HTTP

Estos headers van en cada respuesta del servidor e instruyen al navegador a comportarse de forma más segura. Son una de las medidas de más impacto por menos esfuerzo.

Los que debes agregar hoy mismo

Strict-Transport-Security (HSTS) — fuerza HTTPS en futuros requests:

Strict-Transport-Security: max-age=31536000; includeSubDomains

Una vez que el navegador lo recibe, no intentará conectar por HTTP durante un año.

X-Content-Type-Options — evita que el navegador adivine el tipo MIME:

X-Content-Type-Options: nosniff

Previene ataques donde un archivo JS malicioso se sirve como imagen.

X-Frame-Options — previene clickjacking:

X-Frame-Options: DENY

Impide que tu sitio sea embebido en un <iframe> de otro dominio.

Referrer-Policy — controla qué información de URL se comparte:

Referrer-Policy: strict-origin-when-cross-origin

Permissions-Policy — deshabilita APIs del navegador que no usas:

Permissions-Policy: geolocation=(), microphone=(), camera=()

Configuración en Nginx

server {
  # ... resto de la config

  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header X-Frame-Options "DENY" always;
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
}

Configuración en Express (Node.js)

La librería Helmet agrega automáticamente todos estos headers y más:

npm install helmet
import helmet from 'helmet';

app.use(helmet({
  // Ajusta según tu caso
  contentSecurityPolicy: false, // Deshabilita si tienes CSP propio o scripts inline
  crossOriginEmbedderPolicy: false,
}));

Content Security Policy (CSP): el más poderoso y complejo

CSP es el header más efectivo contra XSS (Cross-Site Scripting), pero también el más difícil de configurar porque bloquea scripts, estilos e imágenes que no estén en la lista blanca.

Un CSP básico para una app con scripts de tu propio dominio:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;

Empieza en modo reporte (Content-Security-Policy-Report-Only) para detectar qué romperías antes de activarlo en producción:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

Verifica tu configuración

Una vez desplegado, usa estas herramientas para verificar:

Un sitio bien configurado debería obtener al menos B+ en Security Headers y A en SSL Labs. No es perfección, es el mínimo razonable para cualquier aplicación en producción.

¿Necesitas ayuda con esto?

En Zerep construimos productos digitales, capacitamos equipos y ayudamos a negocios a crecer en internet.

Ver servicios Contáctanos

Zerep

Agencia de ingeniería de software. Construimos productos digitales, capacitamos equipos y ayudamos a negocios a crecer en internet.

TRABAJA CON NOSOTROS

Cotiza ahora, empieza hoy

Escríbenos y te respondemos en menos de 24 horas. Sin compromisos, sin letras chiquitas.

Ayudamos a tu marca a crecer en redes sociales y buscadores, con el uso de las mejores herramientas del mercado.

Google
Slack
GitHub
GitLab
Atlassian
Spotify
PayPal
Airbnb
Dropbox
Shopify