Los server blocks son el equivalente en Nginx a los virtual hosts de Apache: permiten que un único servidor sirva varios dominios, cada uno con su configuración, su directorio y sus logs. En este tutorial configurarás en Ubuntu 24.04 un server block por defecto que rechaza nombres desconocidos, dos sitios con su propio dominio, una redirección de www, PHP-FPM en uno de ellos, un proxy inverso hacia una aplicación y HTTPS para todos.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS y Nginx instalado desde los repositorios de Ubuntu, con el perfil Nginx Full permitido en UFW. Si aún no lo tienes, sigue primero la guía de instalación de Nginx en Ubuntu 24.04.
  • Un usuario no root con privilegios sudo.
  • Dos dominios con registros DNS A que apunten a la IP pública del servidor, para el dominio raíz y para www. En esta guía se usan site1.your_domain y site2.your_domain: sustitúyelos por los tuyos.

Cómo elige Nginx el server block

Para cada petición, Nginx primero filtra los server blocks por la dirección y el puerto de listen y, entre ellos, compara la cabecera Host con server_name en este orden:

  1. Nombre exacto, como site1.your_domain.
  2. Comodín al principio más largo, como *.site1.your_domain.
  3. Comodín al final más largo, como site1.*.
  4. Primera expresión regular que coincida, en el orden en que aparecen en la configuración (por ejemplo ~^(?<sub>.+)\.your_domain$).

Si nada coincide, responde el server block marcado como default_server en ese puerto, o el primero que cargó si no hay ninguno marcado. Por eso conviene definir uno explícito, como harás en el paso 1.

Paso 1: Crear un server block por defecto

Si alguien accede por la IP o con un dominio que no has configurado, es mejor cerrar la conexión que mostrar uno de tus sitios. Ubuntu trae el sitio default, que muestra la página de bienvenida. Desactívalo:

sudo rm /etc/nginx/sites-enabled/default

Crea en su lugar un server block que responda con el código especial 444, con el que Nginx cierra la conexión sin enviar respuesta:

sudo nano /etc/nginx/sites-available/000-catchall
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

server_name _; es solo un nombre que nunca coincide con un dominio real; lo que convierte a este bloque en el predeterminado es default_server. Actívalo y recarga:

sudo ln -s /etc/nginx/sites-available/000-catchall /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Comprueba que las peticiones por IP se cierran:

curl -I http://127.0.0.1
curl: (52) Empty reply from server

Paso 2: Crear los directorios de los sitios

sudo mkdir -p /var/www/site1.your_domain/html /var/www/site2.your_domain/html
sudo chown -R $USER:$USER /var/www/site1.your_domain /var/www/site2.your_domain
echo '<h1>Sitio 1 funciona</h1>' > /var/www/site1.your_domain/html/index.html
echo '<h1>Sitio 2 funciona</h1>' > /var/www/site2.your_domain/html/index.html

Nginx trabaja como www-data y solo necesita leer: los permisos por defecto (755 en directorios, 644 en archivos) son suficientes. Si una aplicación necesita escribir en un directorio concreto, da permisos a www-data solo en ese directorio, nunca chmod 777.

Paso 3: Crear el server block del primer sitio

Crea el archivo en sites-available:

sudo nano /etc/nginx/sites-available/site1.your_domain
server {
    listen 80;
    listen [::]:80;

    server_name site1.your_domain www.site1.your_domain;
    root /var/www/site1.your_domain/html;
    index index.html;

    access_log /var/log/nginx/site1.your_domain.access.log;
    error_log /var/log/nginx/site1.your_domain.error.log;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ /\.(?!well-known) {
        deny all;
    }
}
  • server_name admite varios nombres separados por espacios.
  • root es el directorio publicado.
  • try_files sirve el archivo o directorio pedido o devuelve 404.
  • La location ~ /\. bloquea archivos ocultos (.git, .env) pero deja pasar /.well-known/, que usa Let's Encrypt.

Paso 4: Crear el server block del segundo sitio

Copia el archivo y cambia el dominio:

sudo cp /etc/nginx/sites-available/site1.your_domain /etc/nginx/sites-available/site2.your_domain
sudo sed -i 's/site1\.your_domain/site2.your_domain/g' /etc/nginx/sites-available/site2.your_domain
grep -E 'server_name|root' /etc/nginx/sites-available/site2.your_domain
    server_name site2.your_domain www.site2.your_domain;
    root /var/www/site2.your_domain/html;

Paso 5: Activar los sitios y comprobarlos

Nginx en Ubuntu carga todo lo que hay en sites-enabled/. Activa los dos sitios con enlaces simbólicos:

sudo ln -s /etc/nginx/sites-available/site1.your_domain /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/site2.your_domain /etc/nginx/sites-enabled/

Con varios nombres largos puedes encontrarte el error could not build server_names_hash, you should increase server_names_hash_bucket_size. Para evitarlo, abre /etc/nginx/nginx.conf y descomenta esta línea del bloque http:

server_names_hash_bucket_size 64;

Comprueba la sintaxis y recarga:

sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Prueba cada dominio sin esperar al DNS gracias a --resolve:

curl --resolve site1.your_domain:80:127.0.0.1 http://site1.your_domain
curl --resolve site2.your_domain:80:127.0.0.1 http://site2.your_domain
<h1>Sitio 1 funciona</h1>
<h1>Sitio 2 funciona</h1>

Para ver qué server_name ha cargado Nginx en total, revisa la configuración efectiva:

sudo nginx -T 2>/dev/null | grep -E '^\s*server_name'
    server_name _;
    server_name site1.your_domain www.site1.your_domain;
    server_name site2.your_domain www.site2.your_domain;

Paso 6: Ejecutar PHP en un sitio con PHP-FPM

Nginx no ejecuta PHP: pasa las peticiones .php a PHP-FPM por un socket Unix. Instálalo:

sudo apt install php8.3-fpm

Edita el server block del sitio 2:

sudo nano /etc/nginx/sites-available/site2.your_domain

Cambia la línea index y añade una location para PHP dentro del bloque server:

    index index.php index.html;

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

El fragmento snippets/fastcgi-php.conf que incluye Ubuntu define SCRIPT_FILENAME y comprueba que el archivo existe antes de pasarlo a PHP, lo que evita ejecutar código a través de rutas manipuladas. Recarga y prueba:

sudo nginx -t
sudo systemctl reload nginx
echo '<?php echo "PHP " . PHP_VERSION . " en site2\n";' > /var/www/site2.your_domain/html/info.php
curl --resolve site2.your_domain:80:127.0.0.1 http://site2.your_domain/info.php
PHP 8.3.6 en site2

Borra el archivo de prueba:

rm /var/www/site2.your_domain/html/info.php

Para una aplicación con controlador frontal (WordPress, Laravel), cambia try_files $uri $uri/ =404; por try_files $uri $uri/ /index.php?$args; en la location /.

Paso 7: Publicar una aplicación con un proxy inverso (opcional)

Si tienes una aplicación escuchando en un puerto local (Node.js, Python, un contenedor), un server block puede publicarla en su propio dominio. Por ejemplo, para app.your_domain con la aplicación en el puerto 3000:

sudo nano /etc/nginx/sites-available/app.your_domain
server {
    listen 80;
    listen [::]:80;
    server_name app.your_domain;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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 Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Las cabeceras Upgrade y Connection permiten WebSockets. Actívalo como los demás:

sudo ln -s /etc/nginx/sites-available/app.your_domain /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Si la aplicación no está arrancada, Nginx devolverá 502 Bad Gateway.

Paso 8: Activar HTTPS

Instala Certbot con el plugin de Nginx:

sudo apt install certbot python3-certbot-nginx

Solicita un certificado por sitio. Certbot encuentra el server block por server_name, añade listen 443 ssl, las rutas del certificado y un bloque que redirige HTTP a HTTPS:

sudo certbot --nginx -d site1.your_domain -d www.site1.your_domain
sudo certbot --nginx -d site2.your_domain -d www.site2.your_domain

Comprueba ambos sitios y la renovación automática:

curl -I https://site1.your_domain
sudo certbot renew --dry-run
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)

Paso 9: Redirigir www al dominio principal

Ahora cada sitio responde igual con y sin www. Para unificar las URL, separa www en su propio server block que solo redirige. Edita el archivo del sitio 1:

sudo nano /etc/nginx/sites-available/site1.your_domain

En el bloque que contiene listen 443 ssl (el que ha modificado Certbot), deja solo el dominio principal:

    server_name site1.your_domain;

Añade al final del archivo un bloque para www en HTTPS que reutiliza el mismo certificado, ya que Certbot lo emitió para ambos nombres:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name www.site1.your_domain;

    ssl_certificate /etc/letsencrypt/live/site1.your_domain/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/site1.your_domain/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    return 301 https://site1.your_domain$request_uri;
}

El bloque del puerto 80 que añadió Certbot ya redirige ambos nombres a HTTPS, así que no hay que tocarlo. Comprueba y recarga:

sudo nginx -t
sudo systemctl reload nginx
curl -I https://www.site1.your_domain/contacto
HTTP/1.1 301 Moved Permanently
Location: https://site1.your_domain/contacto

Repite el cambio para el sitio 2. $request_uri conserva la ruta y los parámetros de la URL original.

Solución de problemas

Un dominio muestra otro sitio o la conexión se cierra: el nombre no coincide con ningún server_name y ha respondido el bloque por defecto. Revisa la salida de sudo nginx -T | grep server_name y comprueba que el dominio aparece exactamente igual y que el enlace existe en sites-enabled/.

conflicting server name "site1.your_domain" on 0.0.0.0:80, ignored: el mismo nombre está en dos server blocks del mismo puerto. Suele pasar al dejar una copia del archivo en sites-enabled/. Localízalo con grep -r "server_name" /etc/nginx/sites-enabled/.

a duplicate default server for 0.0.0.0:80: hay dos bloques con default_server en el mismo puerto, normalmente porque sigue activo el sitio default de Ubuntu. Elimina uno de los dos enlaces.

El navegador descarga el archivo PHP: la location ~ \.php$ no está en el server block que responde (recuerda revisar también el bloque del puerto 443) o php8.3-fpm no está activo.

502 Bad Gateway: Nginx no puede hablar con PHP-FPM o con la aplicación del proxy. Comprueba el servicio (systemctl status php8.3-fpm) o el puerto (sudo ss -tlnp | grep 3000); el log de errores del sitio indica el socket o la dirección exacta que falló.

Conclusión

Has configurado en Nginx sobre Ubuntu 24.04 un server block por defecto que rechaza nombres desconocidos, dos sitios con su propio directorio y logs, PHP-FPM, un proxy inverso, HTTPS con renovación automática y la redirección de www. Como siguientes pasos puedes:

  • Mover a un fragmento de /etc/nginx/snippets/ las location que repites en todos los sitios e incluirlo con include.
  • Crear un pool de PHP-FPM por sitio para aislar aplicaciones de distintos clientes.
  • Añadir limitación de peticiones con limit_req en las rutas de login o de API.