Problema
Este texto detalla los pasos a seguir para conectar un proxy inverso (NGINX) instalado en un servidor A, y dar acceso a servicios web instalados en otra máquina diferente, un servidor B. Si la máquinas están en ubicaciones o redes diferentes, la conectividad la podremos resolver mediante varias alternativas, como una VPN.
En mi caso concreto, esto sirve para instalar NGINX en un VPS, y usarlo para exponer mis servicios alojados en un servidor que tengo en casa, “barcas”. Para conectar las dos máquinas y que NGINX pueda redirigir el tráfico a esos servicios, he usado Tailscale.
El objetivo
Este artículo continúa la serie que empezó con “Cambios en mi red de la mano de XMPP”. Si te preguntas por qué querríamos hacer esto, o qué valor tiene esta configuración, visita ese enlace.
Solución
Para llevar a cabo esta solución necesitarás:
- Un servidor VPS contratado con tu servicio de alojamiento de confianza, al que poder acceder por SSH.
- Un servidor en tu casa, con al menos un servicio aceptando conexiones HTTP. Por ejemplo, en el puerto 5000 (
0.0.0.0:5000). A este servidor lo llamaremos patata en el resto del artículo, por claridad. - Una cuenta gratuita en Tailscale. En general, valdría con cualquier otro proveedor de VPN, pero es preferible que esté basada en WireGuard, porque ofrece un mejor rendimiento que OpenVPN.
- Opcionalmente, o para que esto tenga sentido, un dominio con sus registros de DNS apuntando al VPS que mencionamos en el punto 1.
En las instrucciones de este artículo supondré que ambos servidores (el VPS y patata) tienen instalada una Ubuntu LTS Server (por ejemplo, la 24.04).
Si no hay problemas, en 20 minutos deberías tenerlo todo listo.
Servidor en casa
Nos conectaremos a patata, donde está nuestro servicio web esperando peticiones en el puerto 5000.
Instalamos Tailscale y autenticamos a patata en nuestra cuenta. Esto asociará una dirección IP dentro de la VPN a patata:
# Las órdenes para instalar tailscale no las reproduzco
# porque podrían cambiar en cualquier momento.
sudo tailscale up
# Seguimos las instrucciones por pantalla
# Habilitamos Tailscale como servicio de sistema permanentemente
sudo systemctl enable tailscaled --now
# Obtenemos la IP de esta máquina en la tailnet y la anotamos.
tailscale ip -4
100.85.67.22
En el panel de control de Tailscale podremos ver su nombre local, además de la IP devuelta por la última orden.
También podremos configurar la máquina para que no haya que re-introducirla en la red, desactivando la expiración de la clave usada para autenticar la máquina en Tailscale.
Opción relevante para desactivar la expiración de las claves
Opcionalmente, podemos activar el “MagicDNS” para podernos referir a ambas máquinas mediante un nombre, en lugar de mediante una dirección IP, sin tener que configurar nada localmente en las máquinas.
Opciones de “MagicDNS” en el panel de control de Tailscale. En el ejemplo ya se encuentra activado.
Una vez la máquina está en Tailscale, debemos abrir el puerto 5000 en el cortafuegos de patata, si es que no lo habíamos hecho ya, para que pueda recibir peticiones desde el VPS por dicho puerto.
sudo iptables -A INPUT -p tcp -m tcp --dport 5000 -j ACCEPT
A este respecto:
- Si no tienes
iptables-persistentinstalado, es el momento de instalarlo para poder grabar las reglas de cortafuegos de tal forma que se apliquen con cada reinicio de la máquina.
sudo apt install iptables-persistent
sudo iptables-save > /etc/iptables/rules.v4
- Personalmente, abro los puertos en todos los interfaces y no sólo en el de Tailscale.
- Al no tener que abrir ese puerto en el router, no estoy haciendo nada que me deje demasiado expuesto.
- Al hacerlo así, no necesito acceder por Tailscale a patata cuando, estando en casa, necesito hacer alguna prueba.
- Cada caso es distinto: decide qué hacer tú mismo con tu cortafuegos dependiendo de lo crítico que sea el servicio que estás configurando, y lo sensibles que sean los datos que gestiona.
VPS
En segundo lugar, nos conectamos al VPS por medio de SSH e instalamos un proxy inverso. Aunque no vayamos a tener más de un servicio escuchando en los puertos 80 y 443, configurar y entender un proxy inverso es más fácil que configurar y entender reglas de reenvío de tráfico y DNAT en un cortafuegos como iptables.
En mi caso, suelo usar NGINX.
sudo apt update && sudo apt install nginx
Instalamos Tailscale y repetimos los mismos pasos al respecto que en el caso de patata. Anotamos la dirección IP de el VPS en “la tailnet”, por ejemplo 100.85.77.36.
Obtenemos un certificado de Let’s Encrypt para nuestro dominio, con CertBot. Para esto:
- Debemos configur previamente los registros DNS de nuestro dominio,
ejemplo.com, para que apunten a la IP pública (no a su IP en Tailscale) del VPS en el que estamos trabajando. - Es posible tengamos que parar NGINX para esto (
sudo systemctl stop nginx), dependiendo de nuestros proveedores de dominio y los plugins de CertBot disponibles para interactuar con él.
sudo certbot certonly --standalone -d ejemplo.com
...
...
Successfully received certificate.
Certificate saved at: /etc/letsencrypt/live/ejemplo.com/fullchain.pem;
Key is saved at: /etc/letsencrypt/live/ejemplo.com/privkey.pem;
...
...
Configuramos el servicio en el proxy inverso, poniendo en la sentencia proxy_pass la IP de nuestro servidor patata en Tailscale. Para ello, en /etc/nginx/sites-available/ creamos un fichero de configuración, por ejemplo ejemplo.com.conf para una web https://ejemplo.com.
upstream ejemploweb {
# Sustituye [IP o nombre de máquina] por el valor de tu 'patata'
# **en Tailscale**.
# 100.85.67.22 en el texto del artículo.
server [IP o nombre de máquina]:5000;
keepalive 64;
}
server {
server_name ejemplo.com;
listen 80;
location / {
return 301 https://$host$request_uri;
}
}
server {
server_name ejemplo.com;
listen 443 ssl http2;
# Other SSL stuff goes here
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_certificate /etc/letsencrypt/live/ejemplo.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ejemplo.com/privkey.pem;
location / {
# The final `/` is important.
proxy_pass http://ejemploweb/;
add_header X-Frame-Options SAMEORIGIN;
add_header X-XSS-Protection "1; mode=block";
proxy_redirect off;
proxy_buffering off;
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-Port $server_port;
proxy_read_timeout 90;
}
}
Creamos un enlace simbólico a este fichero en /etc/nginx/sites-enabled, probamos la configuración de NGINX y, si todo va bien, la recargamos.
cd /etc/nginx/sites-enabled/
sudo ln -s ../sites-available/ejemplo.com.conf
sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# Si hemos detenido el servicio de NGINX para obtener el certificado,
# lo arrancamos; si no:
sudo systemctl reload nginx
En este punto, deberíamos poder interactuar con nuestro servicio en patata desde https://ejemplo.com.
Posibles errores que te puedes encontrar
- El navegador no muestra nada, el servicio no contesta. Si hay actividad en los logs de NGINX en el VPS (
sudo tail -f /var/log/nginx/access.log), y la petición se abandona tras un tiempo (timeout), hay varias posibles causas que me he encontrado con mi limitada experiencia. En patata:- El servicio al que quieremos acceder a través de NGINX no está funcionando, y tenemos que arrancarlo,
- o el servicio está expuesto en el puerto 5000 de un interfaz de red específico, en lugar de
0.0.0.0(que significatodos los interfaces de redde patata), y que no es el de Tailscale; por ejemplo, en127.0.0.1, - o hemos hecho algo mal con el cortafuegos y la petición se está rechazando de esta forma.
- La configuración de NGINX es correcta (
sudo nginx -tdice que 👍), pero NGINX no arranca o se muere al recargar la configuración. La causa más frecuente es que NGINX no se puede comunicar con patata, al que estaríamos apuntando con la sentenciaproxy_pass, porque no lo encuentra.- O bien hemos cometido un error al configurar el
proxy_pass, - o bien NGINX ha intentado establecer contacto con patata antes de que Tailscale estableciese el túnel, por ejemplo si esto ocurre tras reiniciar el VPS,
- o bien el servicio de Tailscale se ha parado en alguna de las dos máquinas.
- Esta causa se confirma con el mensaje
host not found in upstreamen la salida desudo systemctl status nginx:
- O bien hemos cometido un error al configurar el
nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/nginx.service.d
└─override.conf
Active: active (running) since Fri 2025-08-29 11:05:51 UTC; 3 days ago
Docs: man:nginx(8)
Process: 1154 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Process: 1156 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Process: 58676 ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload (code=exited, status=1/FAILURE)
Main PID: 1157 (nginx)
Tasks: 3 (limit: 1110)
Memory: 46.9M (peak: 200.2M)
CPU: 29min 57.349s
CGroup: /system.slice/nginx.service
├─1157 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
├─1158 "nginx: worker process"
└─1159 "nginx: cache manager process"
...
Sep 02 05:32:49 vps-hostingprovider nginx[58676]: 2025/09/02 05:32:49 [emerg] 58676#58676: host not found in upstream "100.85.77.22" in /etc/nginx/sites-enabled/ejemlo.com.conf>
Sep 02 05:32:49 vps-hostingprovider systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Sep 02 05:32:49 vps-hostingprovider systemd[1]: Reload failed for nginx.service - A high performance web server and a reverse proxy server.
lines 1-28/28 (END)
- Recibimos un código de estado HTTP 502. Esto quiere decir que, si bien NGINX está funcionando correctamente, el servicio al que nos conectamos (expuesto en el puerto 5000 de patata), está devolviendo errores.
- Si haces una prueba local en patata y el servicio funciona, casi seguro que se trata de un tema de proxies de confianza. Describir el problema en detalle excede los objetivos de este artículo, pero en resumen se trata de que tienes que especificar en la configuración de ese servicio, en patata, que NGINX está instalado en una máquina diferente, el VPS, y configurar la dirección del VPS en Tailscale como proxy de confianza. Consulta los logs del servicio para confirmar esta hipótesis.
- Esto no es un problema, es una característica de seguridad bastante interesante.
- Por ejemplo, esto me pasó con mis instancias de Mastodon y de Nextcloud. En ambos casos, la documentación contenía las soluciones: Mastodon – trusted proxies, Nextcloud – trusted proxies
- Depende de cómo esté implementado el servicio que estamos intentando arreglar, puede ser que acepte un proxy de confianza tanto como nombre de máquina o como direccion IP, o sólo como IP. Ármate de paciencia y si no te funciona con el nombre de la máquina no te desesperes.