Cómo añadir una web Node.js con nginx a un servidor con Apache

Logos de nginx, Node.js y Apache formando una línea ganadora en un juego de tres en raya

En este artículo te explico cómo pasar de una configuración web clásica de Apache en Ubuntu a otra en la que tenemos dos dominios alojados en la misma máquina: uno con Node.js y otro sobre Apache. Para ello usaremos nginx como proxy inverso.

La siguiente imagen muestra la arquitectura de nuestro servidor después del cambio.

Diagrama de bloques de la arquitectura del servidor con nginx distribuyendo peticiones a Apache y a Node.js

1. Cambiar los puertos a Apache

Con la nueva configuración, será nginx quien atienda la conexiones del exterior por los puertos 80 (HTTP) y 443 (HTTPS). Esto quiere decir que, antes de instalarlo, debemos modificar la configuración de Apache para que «escuche» por otros puertos no estándar. De lo contrario, al instalar nginx se produciría un conflicto al intentar escuchar ambos por el mismo puerto.

Para esto, primero debemos localizar la directiva Listen 80, probablemente en /etc/apache2/ports.conf, y cambiarla a un puerto no estándar, digamos el 8000:

Listen 8000

Si hay alguna otra Listen para el puerto SSL 443, la borramos o la comentamos (será nginx quien se encargue de las conexiones seguras, ya que ahora las conexiones con Apache quedan «en casa»).

Si teníamos una sentencia del tipo <VirtualHost *:80> en la configuración del servidor, en algún fichero del estilo /etc/apache2/sites-enabled/example_com.conf, la cambiamos también al nuevo puerto:

<VirtualHost *:8000>

Hechos estos cambios, ya podemos reiniciar el servidor para que surtan efecto:

sudo service apache2 restart

2. Instalar nginx

Hay básicamente dos maneras de instalar nginx: precompilado desde el repositorio oficial (vía apt) y manualmente (compilando los fuentes). En este caso nos es más que suficiente la instalación de la versión estable desde el repo oficial, para lo cual seguiremos las instrucciones de instalación para Ubuntu. En resumen:

sudo apt install curl gnupg2 ca-certificates lsb-release
echo "deb http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" | sudo tee /etc/apt/sources.list.d/nginx.list
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add -
sudo apt update
sudo apt install nginx

Para saber si nginx se está ejecutando:

sudo service nginx status

O bien:

ps -aux | grep nginx

3. Configurar nginx como proxy inverso

La configuración del sitio principal la tenemos normalmente en /etc/nginx/conf.d/default.conf. Ahí incluiremos la redirección permanente (301) de HTTP a HTTPS. Algo así:

server {
    listen       80 default_server;
    listen       [::]:80 default_server;
    server_name  example.com www.example.com;
    return 301   https://example.com$request_uri; # Redireccionar todas las peticiones
}

La configuración de la web segura la podríamos tener en un fichero aparte. Ahí es donde configuraremos nginx como proxy inverso para Apache:

server {
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    server_name  example.com www.example.com;

    ssl_certificate /ruta/de/la/cadena-de-certificados;
    ssl_certificate_key /ruta/de/la/clave-privada;
    # … resto de la configuración de SSL…

    location / {
        # Pasa la petición a Apache
        proxy_pass http://127.0.0.1:8000;
        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 X-Forwarded-Host $host;
    }
}

Con esto, nuestra web volvería a estar accesible desde el exterior tras reiniciar el servidor:

sudo nginx -s reload

Nota: ahora que estamos tras un proxy inverso, posiblemente sea necesario tocar el código del servidor (PHP o lo que sea) para tener en cuenta las cabeceras X-Forwarded-* (para detectar correctamente si la conexión es o no segura —X-Forwarded-Proto— o la verdadera dirección IP del cliente —X-Forwarded-For—, por ejemplo).

4. Instalar Node.js y dependencias

Si aún no tenemos Node.js instalado, seguimos las instrucciones de instalación para Ubuntu. En estos momentos (abril 2019), la última versión con respaldo a largo plazo (LTS) es la 10.x:

curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs

Comprobamos que se ha instalado correctamente, preguntando por la versión:

node --version

En este punto, también deberíamos instalar cualquier otra dependencia que tenga nuestro proyecto Node.js, por ejemplo MongoDB, paquetes npm, etc.

5. Desplegar la aplicación Node.js

En cuanto al código de la aplicación, debemos tener en cuenta dos cosas:

  1. Configurarla para que no interprete la dirección IP del proxy como la del cliente final. En su lugar, debería mirar la cabecera HTTP X-Forwarded-For (que estableceremos en la configuración de nginx en el siguiente paso). En Express, hacemos esto con la variable de aplicación trust proxy.
  2. Por supuesto, hay que programar el servidor para que atienda las peticiones por otro puerto no estándar, digamos el 9000.

Quedaría algo como lo que sigue:

const express = require('express');
const app = express();
const port = 9000;

app.set('trust proxy', true);
//...

app.listen(port, () => console.log(`Servidor activo en el puerto ${port}.`));

Una vez tenemos nuestra aplicación instalada y lista para funcionar, es hora de ejecutarla. Eso sí, lo ideal es registrarla como servicio, de manera que arranque automáticamente si se reinicia el sistema operativo. En Ubuntu, esto es tan sencillo como crear un fichero, p. ej. nodeapp.service, en la ruta /etc/systemd/system. Por ejemplo:

[Unit]
Description=Mi fantabulosa web example.net
# Si la aplicación se conecta a una base de datos MongoDB…
Requires=network.target mongod.service
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/node /home/perez/www/example_net/index.js
WorkingDirectory=/home/perez/www/example_net
# Si la aplicación se aborta por algún motivo, reiniciamos a los tres segundos
Restart=always
RestartSec=3
StandardInput=null
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=example_net-webapp
# Variables de entorno que necesite nuestra aplicación
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

Con esto, ya podemos iniciar la aplicación como servicio:

sudo systemctl start nodeapp

6. Configurar la nueva web en nginx

Ya solo queda configurar nginx para recibir las peticiones a nuestro nuevo dominio, example.net, y actuar como proxy inverso de la web Node.js. Añadiríamos un nuevo fichero, por ejemplo example_net.conf, al directorio de configuración /etc/nginx/conf.d:

server {
    listen       80;
    listen       [::]:80;
    server_name  example.net www.example.net;
    return 301   https://example.net$request_uri; # Redirección HTTP → HTTPS
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name  example.net www.example.net;

    # Configuración SSL…
    # ...

    location / {
        # Pasamos la petición a la aplicación Node.js
        proxy_pass http://127.0.0.1:9000;
        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 X-Forwarded-Host $host;
    }
}

Aplicamos los cambios…

sudo nginx -s reload

Listo

…y listo. Tenemos funcionando, si los dioses nos son propicios, example.com (Apache) y example.net (Node.js) en el mismo servidor (y más que podríamos fácilmente añadir en el futuro). Además, tenemos un flamante nginx que podremos tunear a nuestro antojo para mejorar el rendimiento del servidor (cachés, limitación de peticiones, servir ficheros estáticos, redirecciones, seguridad y un largo etcétera).

Pero ya iremos viendo algunas de esas técnicas en próximos artículos. Ahora lo que toca es un merecido descanso.

Deja un comentario

Tu dirección de correo electrónico no será publicada.

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.