VPS con Debian 13 + Nginx, webs estáticas, seguro, fácil de migrar y mantener

Trabajamos en dos ordenadores distintos, el nuestro que llamamos local y el servidor en la nube que llamamos VPS.

  • PASO 1 usuario + SSH seguro

  • PASO 2 Instala nftables (config básica)

  • PASO 3 Instala fail2ban

  • PASO 4 Instala nginx Comprueba:

    ss -tulpn # Debe salir: 22 80 Nada más.

Arquitectura recomendada para tu caso

Con 4 vCPU y 4GB RAM en Debian 13:

🔹 En el HOST (fuera de Docker)

nginx
certbot
nftables
fail2ban

🔹 En Docker

jitsi (stack completo)
django
postgres
etherpad
redis (si lo necesitas)

Orden correcto ahora

Vamos a hacer esto bien:

- Terminar seguridad base
- Instalar nginx
- Configurar subdominios
- Configurar HTTPS
- Instalar Docker
- Levantar Django
- Levantar Etherpad
- Levantar Jitsi (lo último)    # Jitsi siempre el último.

FASE 1 — Primer acceso y usuario

Terminal LOCAL

ssh root@IP_DEL_SERVIDOR        # Conéctate como root

Terminal VPS

apt update && apt upgrade -y    # Actualiza el sistema
adduser nuevoAdministrador     # crea usuario “nuevoAdministrador”
usermod -aG sudo nuevoAdministrador    # añadir al grupo sudo  
adduser web     # crea usuario “web”. 
                # Nunca trabajarás como root.
                # no rellene datos.
usermod -aG sudo web    # se puede añadir al grupo sudo y luego quitar
su - web                # comprueba
sudo whoami             # debe decir root
sudo userdel -r web     # si necesitas eliminar el usuario

FASE 3 — Firewall del proveedor

Activar este firewall, abrir puertos 22, 80 y 443 cerrar todos los demás puertos.

También se puede cambiar el puerto 22 por otro puerto p.ej. 22222: Permite TCP puerto 22222 (SSH personalizado) con estado NEW, ESTABLISHED.

FASE 4 — Crear claves de acceso SSH, permisos ssh y abrir terminales

Las claves SSH son archivos:

ls ~/.ssh/      # ver si ya tienes alguna
id_rsa          id_rsa.pub      # p.ej. un par clave privada/pública
id_ed25519      id_ed25519.pub  # otro par clave privada/pública
id_ecdsa        id_ecdsa.pub    # otro par clave privada/pública
ssh-keygen -t ed25519 -C "comentario opcional" # generar una nueva? 
# Te pregunta dónde guardarla y si quieres contraseña. 
# Si ya tienes una y no quieres perderla, pon un nombre distinto 
# como /home/miUsuarioLocal/.ssh/id_ed25519_servidor2.
# Varias claves tiene sentido si trabajas con distintos servidores 
# o clientes y quieres poder revocar el acceso a uno sin afectar a los demás, 
# o por seguridad si una clave se compromete.

Terminal LOCAL

ssh-copy-id web@IP      # Copia tu clave SSH, desde tu PC:
# si hay varias claves
ssh-copy-id -i /home/miUsuarioLocal/.ssh/id_ed25519_servidor2.pub web@IP 
ssh web@IP              # Luego prueba

¿Qué necesita un usuario NO root para entrar por clave? Para el usuario user1 por ejemplo, debe existir:

/home/user1/.ssh/authorized_keys

Con permisos correctos y la clave pública correspondiente. Pasos para permitir acceso SSH por clave a usuario no root. Supongamos que el usuario es user1.

adduser user1                       # Crear usuario si no existe
mkdir -p /home/user1/.ssh           # Crear carpeta .ssh
chmod 700 /home/user1/.ssh          # Dar permisos

Copiar clave pública:

a. Desde tu máquina local:

ssh-copy-id -i ~/.ssh/tu_clave.pub user1@IP

b. O manualmente:

nano /home/user1/.ssh/authorized_keys   # Pegas la clave pública.

Luego:

chmod 600 /home/user1/.ssh/authorized_keys
chown -R user1:user1 /home/user1/.ssh

ls -la ~/.ssh
drwx------  2 user1 user1 4096 ... .ssh
-rw-------  1 user1 user1  571 ... authorized_keys

Permisos correctos

drwx------ → 700
d = directorio
rwx para el propietario
--- para grupo
--- para otros

-rw------- → 600
= archivo
rw para propietario
--- grupo
--- otros

También comprueba esto (muy importante)

ls -ld ~    # Tu carpeta home NO debe ser escribible por otros. 
            # Lo normal es:
drwxr-xr-x  # Eso es 755 → correcto.
drwxrwxrwx  # Si fuera algo como SSH fallaría. 
            # Eso es EXACTAMENTE lo que SSH exige.

¿Es mejor usar usuario normal en vez de root? -> Sí. Mucho mejor.

Configuración profesional:

PermitRootLogin no
PasswordAuthentication no

Y trabajar así:

ssh user1@IP
sudo -i

Eso reduce muchísimo el riesgo. Diferencia práctica:

Acceso root directo -> recomendado solo en emergencia
Acceso usuario + sudo -> recomendado Producción profesional

Paso 1. Comprueba que tu clave funciona

LOCAL. Desde tu máquina local:

ls ~/.ssh/
ssh -i ~/.ssh/tu_clave root@IP_DEL_SERVIDOR

Si entra sin pedir contraseña → seguimos.

Paso 2. Abre una segunda sesión SSH

Mantén abiertas:

Terminal A (la actual)

Terminal B (nueva conexión con clave)

Ahora tienes red de seguridad.

Paso 3. Copia de seguridad sshd_config del servidos

SERVIDOR:

cat ~/.ssh/authorized_keys  # Comprueba que tienes clave configurada
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak   # copia de seguridad

Si algo falla:

sudo cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
sudo systemctl restart ssh

FASE 5 — Endurecimiento SSH sin riesgo. Vamos a desactivar login por contraseña en el servidor

En terminal del SERVIDOR:

sudo nano /etc/ssh/sshd_config       # Edita.

Asegúrate de que tienes:

PubkeyAuthentication yes # Autenticación por clave (debe estar activa)
PasswordAuthentication no # Desactivar contraseña
PermitRootLogin no      # Root no puede entrar por SSH nunca. 
PermitRootLogin prohibit-password   # Significa que 
                                    # Root puede entrar SOLO con clave, nunca con contraseña

ChallengeResponseAuthentication no
UsePAM yes

MaxAuthTries 3
LoginGraceTime 30
AllowUsers root u007

Guarda: Ctrl+O → Enter → Ctrl+X

No reinicies aún.

Validación antes de reiniciar (CLAVE). Haz esto:

sudo sshd -t        # Si no devuelve nada → configuración válida.
                    # Si devuelve error → NO reinicies y corrige.

Reiniciar servicio (Tenemos dos terminales abiertos)

sudo systemctl restart ssh   # Recarga SSH (sin cortar sesión)

PRUEBA CRÍTICA. En una TERCERA terminal nueva desde tu máquina local:

ssh user1@IP_DEL_SERVIDOR   # Si estamos usando user1
ssh root@IP_DEL_SERVIDOR    # Si entra → éxito total. (Si usamos root)
                            # Solo cuando lo confirmes, 
                            # puedes cerrar las otras sesiones.

PLAN DE EMERGENCIA. Si algo falla, entra al panel de nuestro proveedor de Servicios y abre consola web y restaura backup del sshd_config

sudo cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config # copia configur inicial
sudo systemctl restart ssh

Extra (nivel profesional reduce ataques automáticos) finalmente puedes dejarlo así:

sudo nano /etc/ssh/sshd_config       # Edita.

X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no

MaxAuthTries 3
LoginGraceTime 30
MaxSessions 2
UseDNS no 

Guarda: Ctrl+O → Enter → Ctrl+X

Probar

sudo sshd -t                    # Si no devuelve nada → configuración válida.
sudo systemctl restart ssh      # Recarga SSH (sin cortar sesión)
ssh user1@IP_DEL_SERVIDOR   # Si estamos usando user1
ssh root@IP_DEL_SERVIDOR    # Si entra → éxito total. (Si usamos root)
                            # Solo cuando lo confirmes, 
                            # puedes cerrar las otras sesiones.

Dejamos copia de la nueva configuración.

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.no_passwd

También se puede limitar SSH por firewall (nftables solo desde tu IP)

pero hoy día la mayor parte de servicios de conexión a internet, novistar, vodafone, orange, ... se ofrecen sin IP fija, por ello no vamos a utilizar esta opción.

FASE 6. facilitar acceso al servidor.

LOCAL terminal Copia de seguridad de la configuración ssh y actualizar.

sudo cp /home/user1/.ssh/config /home/user1/.ssh/config.$(date +%F).orig.bak 
                    # hacer copia seguridad

sudo nano ~/.ssh/config # Edita

Pegar:

Host vps-web
    HostName IP
    User web
    Port 22
    IdentityFile ~/.ssh/vps_debian
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

Guarda: Ctrl+O → Enter → Ctrl+X

Y podemos usar el explorador de archivos NAUTILUS de Ubuntu para acceder al VPS

# + Otras ubicaciones
# Escriba la dirección del servidor:
fstp://vps-web

LOCAL Conectar, abrir dos sesiones LOCAL y hacer copia de seguridad:

ssh vps-web # Si entra sin pedir password → OK
sudo cp /home/userLocal/.ssh/config /home/userLocal/.ssh/config.$(date +%F)-ok.bak

CHECKLIST SSH

LOCAL

ssh usuario@IP_DEL_SERVIDOR ¿Tu clave realmente funciona?

Debe:

  • NO pedir contraseña
  • Entrar directamente
  • Mostrar tu usuario correcto con whoami
  • Si pide password → algo no está bien.

Verifica que realmente está usando clave

Una prueba más técnica:

ssh -v usuario@IP_DEL_SERVIDOR

Busca algo como:

  • Offering public key
  • Authentication succeeded (publickey)

Eso confirma que no está usando password.

Verifica que root NO puede entrar con contraseña

En el servidor, revisa:

sudo grep -E 'PasswordAuthentication|PermitRootLogin' /etc/ssh/sshd_config

Lo ideal sería ver algo como:

PasswordAuthentication no
PermitRootLogin prohibit-password

Validación de configuración (crítica), en el servidor:

sudo sshd -t
                # Si no devuelve nada → configuración válida.
                # Si devuelve error → no reiniciar.

Verifica puertos abiertos

En el servidor:

sudo ss -tulpn | grep ssh

Debe mostrar algo como:

LISTEN 0 128 0.0.0.0:22

Simulación de ataque (mini test). Desde tu máquina local intenta forzar password:

ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no usuario@IP
# Debe fallar inmediatamente. Si entra → PasswordAuthentication sigue activo.

Vamos a comprobar la configuración REAL cargada. En el servidor ejecuta:

sudo sshd -T | grep passwordauthentication

Esto muestra la configuración efectiva. Debe devolver:

passwordauthentication no

Si devuelve:

passwordauthentication yes

Entonces está activado en algún archivo adicional.

ls /etc/ssh/sshd_config.d/
50-cloud-init.conf

sudo cat /etc/ssh/sshd_config.d/50-cloud-init.conf 
PasswordAuthentication yes

sudo nano /etc/ssh/sshd_config.d/50-cloud-init.conf
PasswordAuthentication no

ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no usuario@IP
# Debe fallar inmediatamente. Si entra → PasswordAuthentication sigue activo.

FASE 7 — Firewall con nftables "permisivo"

nftables (firewall)

VPS terminal

sudo apt update
sudo apt install nftables -y            # instalar
sudo systemctl enable --now nftables    # activar
sudo systemctl start nftables
sudo systemctl status nftables          # verificar

sudo cp /etc/nftables.conf /etc/nftables.conf.ori.bak
sudo cp /etc/nftables.conf /etc/nftables.conf.$(date +%F).bak

ls -l /etc/nftables.conf*       # Comprobar que se creó bien

# Deberías ver:
# nftables.conf
# nftables.conf.bak

Restaurar si algo falla

sudo cp /etc/nftables.conf.bak /etc/nftables.conf # Restaurar si algo falla
sudo systemctl reload nftables  #vuelves al estado anterior.

Antes de aplicar cambios grandes:

sudo nft -c -f /etc/nftables.conf   # valida la config sin activarla.

Si hay errores → no se aplica → no te quedas sin SSH.

Crear configuración limpia

sudo nano /etc/nftables.conf            # Configuración base

VPS Contenido mínimo seguro:

#!/usr/sbin/nft -f

# Borra todas las reglas cargadas antes.
flush ruleset

# Crea una tabla llamada filter en familia inet.
# inet = IPv4 + IPv6 (ambos a la vez) 
table inet filter {

# Cadena INPUT (lo que entra al servidor)
chain input {
    # Define la “puerta de entrada”.
    # hook input → tráfico que entra al servidor
    # policy drop → por defecto, bloquear todo
    # Todo prohibido
    #  Solo lo que yo permita
    type filter hook input priority 0;
    policy drop;

    # Permite tráfico local (localhost).
    iif lo accept

    # Permite conexiones ya iniciadas. Ejemplo:
    # Tú → servidor → responde → vuelve a ti
    ct state established,related accept

    # Abre el puerto 22 (SSH) a todo el mundo.
    tcp dport {22,22222} accept
    
    # Limita a 3 intentos de conexión SSH cada 60 segundos por IP
    #tcp dport 22 ct state new limit rate 3/minute accept
    
    # Web
    tcp dport {80,443} accept

    # Ping Permitir ping
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept
}
# Bloquear reenvío. Impide que tu VPS actúe como router.
chain forward {
    type filter hook forward priority 0;
    policy drop;
}

# Permitir salida. Permite que el servidor salga a Internet.
# Ejemplo: Actualizaciones, Certbot, DNS, Git, Backups
chain output {
    type filter hook output priority 0;
    policy accept;
}
}

Guardar y salir (nano ctrl+o y ctrl+x)

Servidor:

sudo nft -c -f /etc/nftables.conf       # Si no devuelve error → ok.
sudo nft -f /etc/nftables.conf          # Aplicar
sudo nft list ruleset                   # Verificar
sudo cp /etc/nftables.conf /etc/nftables.conf.hardening.bak

Solo SSH + Web, todo lo demás bloqueado

VPS

ss -tulpn

Netid State  Recv-Q Send-Q       Local Address:Port   Peer Address:Port Process 
udp   UNCONN 0      0               127.nn.nn.nn:nn          0.nn.nn.nn:*            

FASE 8 — ORDEN PROFESIONAL DE MONTAJE VPS

1 Base del sistema (higiene)

sudo apt update
sudo apt upgrade -y
sudo apt autoremove -y
sudo apt install curl -y

2 Instalar fail2ban. Comprobar que está activo

sudo apt update
sudo apt install fail2ban -y
sudo systemctl status fail2ban      # Debe decir active (running).
sudo systemctl start fail2ban
sudo fail2ban-client status sshd

Ahora mismo Fail2ban está casi “en blanco”. No está bloqueando casi nada todavía. Necesita configuración. Crear config local (nunca editar defaults) Fail2ban funciona así:

/etc/fail2ban/jail.conf → original (NO tocar)
/etc/fail2ban/jail.local → tu config 

Recomendado endurecer un poco más. Ahora mismo usas defaults. Podemos mejorarlos. Ajustar:

  • Bloqueo más largo

  • Menos intentos

      sudo nano /etc/fail2ban/jail.local
    
[DEFAULT]

bantime = 12h
findtime = 10m
maxretry = 3
backend = systemd
banaction = nftables-multiport

[sshd]
enabled = true

Guardar y salir (nano ctrl+o y ctrl+x)

sudo systemctl restart fail2ban

Ahora Tiempo ban Ahora 12h antes 1h, intentos, ahora 3 antes 5-6 Bots = fuera todo el día. Ver ataques en tiempo real (opcional, muy educativo)

sudo tail -f /var/log/fail2ban.log

Verificar que está protegiendo SSH

sudo fail2ban-client status     # Debe mostrar algo como:

Jail list: sshd

Luego:

sudo fail2ban-client status sshd        # Debe mostrar:

Currently banned: 0
Total banned: 0

Instalar y configurar utilidades y seguridad

VPS terminal

nano
which nano          # nano está instalado
/usr/bin/nano
mc y fastfech
sudo apt install -y \
    fail2ban \
    curl \
    nftables \
    screen \
    mc
    fastfetch
    unattended-upgrades
    cron
    rsync
    tmux
unattended-upgrades
sudo apt install unattended-upgrades
            # unattended-upgrades is already the newest version (n.nn).
sudo systemctl status unattended-upgrades

No necesitas tocar nada. Los parches de seguridad se aplicarán automáticamente. No utilizan cron, utilizan:

    systemctl status apt-daily.timer
    systemctl status apt-daily-upgrade.timer

No necesitas cron para esto. Tu sistema se actualiza solo automáticamente.

Idiomas

echo $LANG echo $LC_ALL locale

Tienes C.UTF-8 que es el locale neutro por defecto. Para un VPS web es perfectamente válido y tiene ventajas.

  • Los errores salen en inglés, más fácil buscarlos
  • Nginx, fail2ban y los logs todos en inglés
  • Sin problemas de caracteres raros en scripts Recomendación -> déjalo como está.
  • No hay ninguna razón técnica para cambiarlo en un VPS que solo va a servir una web.
  • El español solo tendría sentido si ejecutas aplicaciones que muestran texto al usuario final directamente desde el servidor, que no es tu caso.

FASE 15 — Backups

VPS

sudo mkdir -p /opt/backups

Backup solo configuración + usuarios:
-C / significa cambiar de directorio raíz antes de comprimir. Sin -C /, tar intentaría comprimir rutas absolutas como /etc/, /home/, etc, lo que causa advertencias. con -C /:

  • Archivo más portable - puedes restaurar en cualquier sistema
  • Sin warnings - no dice "Removing leading /"
  • Más seguro - evita sobrescribir archivos del sistema accidentalmente

Hacer un Backup base minimalista limpio. Esto te sirve como punto de partida antes de montar Docker u otras cosas

sudo tar czf /opt/backups/system_config_$(date +%F).tar.gz \
-C / etc home root srv var/spool/cron/crontabs 

# -C / cambiar a raíz comprimir estos directorios

Crear versión base permanente

sudo cp /opt/backups/system_config_YYYY-MM-DD.tar.gz /opt/backups/system_config_clean.tar.gz

Verificar archivo

ls -lh /opt/backups/system_*.tar.gz

tar tzf /opt/backups/system_*.tar.gz | head

- `tar` - programa de archivos
- `c` - crear archivo
- `t` - **list** (listar contenido)
- `z` - **gzip** (leer archivos .gz comprimidos)
- `f` - **file** (archivo siguiente)
- `~/backups/*_system_*.tar.gz` - archivos que coincidan con el patrón
- `| head` - mostrar solo las primeras 10 líneas


# Ver todo el contenido
tar tzf /opt/backups/full_system_*.tar.gz

# Ver cuántos archivos hay
tar tzf /opt/backups/full_system_*.tar.gz | wc -l

# Ver solo archivos de configuración
tar tzf /opt/backups/full_system_*.tar.gz | grep etc/

# Buscar un archivo específico
tar tzf /opt/backups/full_system_*.tar.gz | grep "nginx.conf"

Recuperar backup

sudo apt update
sudo apt upgrade -y
sudo apt autoremove -y
Crear usuarios
sudo useradd -m -s /bin/bash -G sudo -c "usuario1" usuario1
sudo useradd -m -s /bin/bash -G sudo  -c "" usuario2
sudo passwd usuario1
sudo passwd usuario2
Instalar paquetes
sudo apt install -y \
    fail2ban \
    curl \
    nftables \
    screen \
    mc
    fastfetch
    unattended-upgrades
    cron
    rsync
    tmux
Restaurar home del backup
sudo mkdir -p /opt/backups && cd /opt/backups

#copiar el archivo 
Restaurar configuracion del backup
sudo tar xzf config_users_2026-02-28.tar.gz -C / etc home root srv
Fijar permisos
sudo chown -R tu_usuario:tu_usuario /home/tu_usuario/"
sudo chown -R root:root /root/"
sudo chown -R root:root /srv/"

FASE 16 - Nginx y dominios

sudo apt update
sudo apt install nginx -y         #  Instalar nginx
sudo systemctl enable nginx
sudo systemctl start nginx
systemctl status nginx             # Comprobar que funciona

nano /var/www/html/index.html

Pegar

    <!DOCTYPE html>
    <html lang="es">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Servicio en mantenimiento | web1.com -I</title>
    </head>
    <body>

            <h1>🔧 Servicio en mantenimiento web.com</h1>
            
            <p>Estamos realizando tareas de mantenimiento para mejorar tu experiencia.</p>
            <p>El servicio volverá a estar disponible en breve.</p>

                Trabajando en ello...
    </body>
    </html>

Guardar y salir (nano ctrl+o y ctrl+x)

Redirigir dominios a la IP. En el panel donde compraste el dominio, busca DNS / Zona DNS / Registros. Añade:

Registro A
Tipo	Nombre	Valor
A	@	TU_IP
A	www	TU_IP

Abre en el navegador http://midominio.com → deberías ver la página de nginx por defecto o si ya la has creado a /var/www/html/index.html debería ver lo que has puesto en tu index.html

FASE 17 - Certbot letsencrypt

Instalar certbot

sudo apt update
sudo apt install -y certbot python3-certbot-nginx
certbot --version

Lo que debes comprobar antes de pedir los certificados

Haremos copia de las configuraciones originales.
sudo mkdir /etc/nginx/backups
sudo cp /etc/nginx/sites-available /etc/nginx/backups/sites-available
sudo cp /etc/nginx/sites-enabled /etc/nginx/backups/sites-enabled
sudo cp /etc/nginx/nginx.conf /etc/nginx/backups/nginx.conf
1. Comprobar

Que exista:

/var/www/html/index.html

Verifica que el site activo (probablemente default)

sudo nano /etc/nginx/sites-availables/default

y tenga:

root /var/www/html;
index index.html index.htm;

Añadir

server {
    listen 80;
    listen [::]:80;

    server_name dominio.com www.dominio1.com subdominio1.dominio1.com;

    root /var/www/html;
    index index.html;
}

Guardar y salir (nano ctrl+o y ctrl+x)

2. Probar configuración. Siempre antes:
sudo nginx -t

Si dice:

syntax is ok
test is successful

Entonces:

sudo systemctl reload nginx
Antes de ejecutar certbot, verifica SOLO esto:
sudo mkdir -p /var/www/html/.well-known/acme-challenge
sudo chown -R www-data:www-data /var/www/html/.well-known
3. Cómo emitir los certificados (varios certificados separados)

Certificado 1

sudo certbot certonly --webroot \
-w /var/www/html \
-d dominio1.com \
-d wwww.dominio1.com

Comando recomendado (modo profesional)

sudo certbot certonly --webroot \
-w /var/www/html \
-d dominio1.com \
-d www.dominio1.com \
--email tuemail@dominio.com \
--agree-tos \
--no-eff-email \
--non-interactive

Qué hace cada parámetro

--email → obligatorio en modo no interactivo
--agree-tos → acepta términos automáticamente
--no-eff-email → no te suscribe a mailing
--non-interactive → evita preguntas

Resultado correcto:

...
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/dominio1.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/dominio1.com/privkey.pem
This certificate expires on 2026-05-30.
....

Comprueba que certbot tiene renovación automática activa:

sudo systemctl list-timers | grep certbot

respuesta ->

Sun 2026-03-01 15:52:36 UTC 4h 12min Sun 2026-03-01 05:35:25 UTC       6h ago certbot.timer                certbot.service

Veamos que funciona la renovación

sudo certbot renew --dry-run

respuesta ->

Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/....conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Account registered.
Simulating renewal of an existing certificate for .... and .....

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded: 
/etc/letsencrypt/live/..../fullchain.pem (success)

importante

Si es la primera vez que usas certbot en el servidor, ejecuta primero:

sudo certbot register \
--email tuemail@dominio.com \
--agree-tos \
--no-eff-email

Esto registra la cuenta de Let's Encrypt una sola vez.

FASE 18 Configuramos nginx para cambiar de http a https

Creamos archivo nuevo:

sudo nano /etc/nginx/sites-available/dominio1.com

Y pegas esto:

# ===============================
# dominio1.com
# ===============================

# HTTP → redirección a HTTPS
server {
   listen 80;
   listen [::]:80;

   server_name dominio1.com www.dominio1.com;

   return 301 https://$host$request_uri;
}

# HTTPS
server {
   listen 443 ssl;
   listen [::]:443 ssl;

   http2 on;

   server_name dominio1.com www.dominio1.com;

   root /var/www/html;
   index index.html;

   ssl_certificate /etc/letsencrypt/live/dominio1.com/fullchain.pem;
   ssl_certificate_key /etc/letsencrypt/live/dominio1.com/privkey.pem;

   ssl_protocols TLSv1.2 TLSv1.3;
   ssl_prefer_server_ciphers on;

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

   # Logs para depuración (Útiles para ver si Docker responde)
   access_log /var/log/nginx/access.log;
   error_log /var/log/nginx/error.log;

}

Guardar y salir (nano ctrl+o y ctrl+x)

Activarlo

sudo ln -s /etc/nginx/sites-available/dominio1.com /etc/nginx/sites-enabled/

Desactivar el default, muy importante para evitar conflictos:

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

Comprobar configuración

sudo nginx -t

Debe salir limpio sin warnings. Y luego:

sudo systemctl reload nginx

Ahora prueba en navegador

http://dominio1.com
https://dominio1.com
https://www.dominio1.com

También puedes Probar

curl http://dominio1.com
curl -i http://dominio1.com
curl -L http://dominio1.com           # comprobar que resuelve

Debe:

Redirigir desde HTTP automáticamente
Mostrar la página de mantenimiento
Tener candado válido

Comprueba que nftables está funcionando

sudo nft list ruleset # Debes tener -> tcp dport 80 accept y tcp dport 443 accept    
 sudo tar czf /opt/backups/system_https_$(date +%F).tar.gz \
    -C / etc home root srv var/spool/cron/crontabs 

    # -C / cambiar a raíz comprimir estos directorios

Crear versión base permanente

    sudo cp /opt/backups/system_https_YYYY-MM-DD.tar.gz /opt/backups/system_https_clean.tar.gz

FASE 19 - Crear esctructura.

sudo mkdir -p /srv/docker/{django,etherpad,jitsi}
sudo chown -R $USER:$USER /srv/docker

FASE 20 - Instalar docker

No uses apt install docker.io. Instalamos Docker oficial (docker-ce).

🐳 Paso 1 — Limpiar posibles restos

sudo apt remove docker docker-engine docker.io containerd runc

Respuesta:

Package 'docker' is not installed, so not removed
Error: Unable to locate package docker-engine

🐳 Paso 2 — Instalar dependencias

sudo apt update
sudo apt install ca-certificates curl gnupg

🐳 Paso 3 — Añadir clave oficial Docker

sudo install -m 0755 -d /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/debian/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

sudo chmod a+r /etc/apt/keyrings/docker.gpg

🐳 Paso 4 — Añadir repositorio oficial

echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

🐳 Paso 5 — Instalar Docker CE

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

🐳 Paso 6 — Activar y comprobar

sudo systemctl enable docker
sudo systemctl start docker
sudo systemctl status docker

🐳 Paso 7 — Permitir usar docker sin sudo

sudo usermod -aG docker $USER

Después:

newgrp docker

(O cerrar sesión y volver a entrar)

🧪 Probar que funciona

docker run hello-world

Debe descargar imagen y mostrar mensaje de éxito.

FASE 21 — tmux monitorización y gestion

Como estás gestionando una infraestructura, te recomiendo mucho usar paneles (splits) en tmux. Es muy útil tener varios paneles en un solo terminal.

Comandos rápidos en tmux: Ctrl+b y d nluego en el terminal tmux kill-server Ctrl+b y luego %: Divide la pantalla en vertical. Ctrl+b y luego ": Divide la pantalla en horizontal. Ctrl+b y luego o: Salta entre paneles

Lo ideal es configurar tmux para que, con un solo comando, te abra un "centro de mando" donde veas todo tu ecosistema Nginx, Redes, logs, htop de un vistazo.

Aquí tienes una guía rápida y un script para automatizarlo.

  1. Guía Rápida de Supervivencia (Atajos Pro)

     En tmux, todo empieza con el prefijo Ctrl + b.
     Acción	Atajos
     Dividir Vertical	Ctrl + b seguido de %
     Dividir Horizontal	Ctrl + b seguido de "
     Navegar entre paneles	Ctrl + b seguido de Flechas o o
     Cerrar panel	Escribe exit o Ctrl + d
     Zoom en un panel	Ctrl + b seguido de z (repite para volver)
     Desacoplar (Salir sin cerrar)	Ctrl + b seguido de d
     Volver a entrar	tmux attach
     tmux attach → tmux a (o tmux att)
     tmux new-session → tmux new
     tmux list-sessions → tmux ls
     tmux detach → tmux det
    

Script de Configuración: "El Centro de Mando"

Crea un archivo llamado ~/bin/mimonitor.sh en tu carpeta personal del VPS. Este script abrirá una o mas sesiones de tmux dividida en paneles estratégicos.

nano ~/bin/mimonitor.sh

    #!/bin/bash
    SESSION="infra"
    CONTAINER="django-infra"

    # 1. Comprobar si el contenedor está corriendo
    STATUS=$(docker inspect -f '{{.State.Running}}' $CONTAINER 2>/dev/null)

    if [ "$STATUS" != "true" ]; then
        echo -e "\e[31m[!] Alerta: El contenedor $CONTAINER no parece estar activo.\e[0m"
        echo "Iniciando tmux de todos modos para revisión..."
        sleep 1
    fi

    # 2. Comprobar si la sesión ya existe
    tmux has-session -t $SESSION 2>/dev/null

    if [ $? -ne 0 ]; then
        # Crear sesión y nombrar la primera ventana como "Monitor"
        tmux new-session -d -s $SESSION -n "Monitor"

        # --- VENTANA 1: MONITORIZACIÓN ---
        # Panel superior izquierda: Fastfetch
        tmux send-keys -t $SESSION:Monitor "fastfetch" C-m
        
        # Dividir horizontal para Logs (abajo)
        tmux split-window -v -t $SESSION:Monitor
        tmux send-keys -t $SESSION:Monitor "docker logs -f $CONTAINER" C-m
        
        # Dividir vertical el panel superior para Nginx (derecha)
        tmux split-window -h -t $SESSION:Monitor.0
        tmux send-keys -t $SESSION:Monitor "sudo tail -f /var/log/nginx/django_access.log" C-m

        # --- VENTANA 2: DESARROLLO ---
        # Crear nueva pestaña llamada "Codear" en la carpeta del proyecto
        # -c especifica el directorio de inicio
        tmux new-window -t $SESSION -n "Codear" -c "/srv/mi-infra/app"
        tmux send-keys -t $SESSION:Codear "ls -F" C-m
    fi

    # 3. Seleccionar la ventana principal y entrar
    tmux select-window -t $SESSION:Monitor
    tmux a -t $SESSION

Guardar y salir (nano ctrl+o y ctrl+x)

chmod +x ~/bin/mimonitor.sh

añade export PATH="$HOME/bin:$PATH" al final de tu archivo

nano ~/.bashrc

export PATH="$HOME/bin:$PATH"

Luego haz source ~/.bashrc y ya podrás lanzarlo simplemente escribiendo abre.esm desde cualquier carpeta.

Configuración Visual (.tmux.conf)

Para que tu tmux en Debian 13 se vea profesional y sea más fácil de usar con el ratón, crea el archivo nano ~/.tmux.conf y pega esto:

# Habilitar el ratón para redimensionar y seleccionar paneles
set -g mouse on

# Mejorar los colores
set -g default-terminal "screen-256color"

# Barra de estado personalizada (Estilo "Chalet")
set -g status-bg black
set -g status-fg cyan
set -g status-left-length 20
set -g status-left "#[fg=green]VPS: #[fg=white]#H "
set -g status-right "#[fg=yellow]Red: 172.20.10.200 #[fg=white]| %H:%M"

# Cambiar el color del panel activo para no perderse
set -g pane-active-border-style fg=brightblue

Guía de navegación para tu nueva interfaz:

- Ctrl + b y luego n: Siguiente ventana (next).
- Ctrl + b y luego p: Ventana anterior (previous).
- Ctrl + b y luego 0 o 1: Ir directamente al número de ventana.
- Cambiar de pestaña (Monitor <-> Codear): Ctrl + b y luego n (Next) o p (Previous).
- Moverte entre los 3 paneles del Monitor: Ctrl + b y luego las flechas del teclado.
- Hacer "Zoom" en los logs: Si ves algo raro en los logs y quieres pantalla completa, ve al panel de logs y pulsa Ctrl + b y luego z. Repite para volver al mosaico.
- Salir (Seguro): Ctrl + b y luego d. Todo seguirá funcionando en el VPS.
  1. Salida "Modo Cierre Total" (Cerrar sesión)

Si quieres que la sesión de tmux desaparezca y que los comandos que tenías abiertos (como el tail de los logs) se detengan.

Opción A (Panel a panel): Escribe exit en cada panel hasta que se cierren todos. Cuando cierres el último, tmux se apagará solo.

Opción B (Comando rápido): Si estás fuera o en un panel y quieres matar toda la sesión de golpe:

tmux kill-session -t infra

Opción C (Desde dentro): Ctrl + b y luego escribe :kill-session (con los dos puntos delante) y pulsa Enter.

Si quieres limpiar todas las sesiones de golpe sin escribir el nombre (asumiendo que solo tienes una o quieres cerrarlas todas), el comando más rápido es:

tmux kill-server

Tabla de atajos resumidos para gestión de sesiones

Si te gusta ahorrar pulsaciones de teclas, aquí tienes los equivalentes:

Comando Completo	Versión Resumida	Qué hace
tmux attach-session -t	tmux a -t	Entrar en una sesión
tmux list-sessions	tmux ls	Ver qué sesiones hay vivas
tmux new-session -s	tmux new -s	Crear sesión con nombre
tmux kill-session -t	tmux kill-ses -t	Cerrar una sesión concreta
tmux rename-session -t	tmux rename -t	Cambiar el nombre a la sesión

FASE 22 — monitorización básica

VPS

sudo apt install htop iotop iftop vnstat logwatch
  • CPU / RAM / disco

  • Servicios caídos

  • Logs claros

  • Alertas simples

  • Todo local (sin SaaS raro)

      htop
      systemctl status nginx
      systemctl status ssh
      systemctl status fail2ban
      sudo journalctl -f
    
      sudo apt install vnstat
      sudo systemctl enable vnstat
      sudo systemctl start vnstat
      sudo systemctl status vnstat
    

Crear carpeta scripts

mkdir -p ~/bin          # crear carpeta de scripts
nano ~/bin/monitor.sh   # crar script

Pegar

#!/bin/bash

INFORME_DIR="$HOME/logs"
INFORME="$INFORME_DIR/accesos-vps.log"

mkdir -p "$INFORME_DIR"

{

echo "════════════════════════════════════════"
echo "📅 ENTRADA: $(date '+%Y-%m-%d %H:%M:%S')"
echo "════════════════════════════════════════"

echo ""
echo "💾 MEMORIA:"
free -h | awk 'NR==2{printf "   %s / %s (Libre %s)\n", $3, $2, $4}'

echo ""
echo "💿 DISCO:"
df -h / | awk 'NR==2{printf "   %s / %s (Libre %s)\n", $3, $2, $4}'

echo ""
echo "⚡ CPU:"
uptime | awk -F'load average:' '{print "   "$2}'

echo ""
echo "🌍 IP de este VPS:"
hostname -I | awk '{print "   "$1}'

echo ""
echo "🌐 CONEXIONES SSH ACTIVAS:"
ss -tn state established | grep ":22" 
echo "   Total: $(ss -tn state established | grep -c ":22") sesiones"

echo ""
echo "👤 SESIONES ABIERTAS:"
who | awk '{print "   "$0}'
echo ""
echo "🔑 TU SESIÓN ACTUAL:"
if [ -n "$SSH_CLIENT" ]; then
    echo "   $SSH_CLIENT"
else
    echo "   (no es sesión SSH)"
fi

echo ""
echo "🛡️ Fail2ban:"
sudo fail2ban-client status sshd 2>/dev/null | grep -E "Currently|Total|Banned" || echo "   No data"

echo ""
echo "💾 BACKUPS:"

BACKUP_DIR="$HOME/web-backup/snapshots"
LOG="$HOME/web-backup/logs/backup.log"

# Último backup
LAST=$(ls -1dt "$BACKUP_DIR"/20* 2>/dev/null | head -1)

if [ -z "$LAST" ]; then
  echo "   ❌ Sin backups"
else
  DATE=$(basename "$LAST")
  SIZE=$(du -sh "$LAST" | cut -f1)
  AGE=$(( ( $(date +%s) - $(stat -c %Y "$LAST") ) / 3600 ))

  echo "   📅 Último: $DATE"
  echo "   📦 Tamaño: $SIZE"
  echo "   ⏱️ Edad: ${AGE}h"

  if [ "$AGE" -gt 48 ]; then
    echo "   ⚠️ ALERTA: Backup antiguo"
  else
    echo "   ✅ OK"
  fi
fi


# Último error
if grep -q "rsync error" "$LOG" 2>/dev/null; then
  echo "   ❌ Errores detectados"
  tail -3 "$LOG" | sed 's/^/   /'
else
  echo "   🟢 Sin errores recientes"
fi


echo ""
echo "📦 Updates:"
apt list --upgradable 2>/dev/null | grep -c "/" | awk '{print "   "$1}'

echo ""
echo "📊 TRÁFICO RED:"
vnstat

echo ""
echo "🔐 Últimos OK:"
{
  sudo journalctl -u ssh --no-pager 2>/dev/null | grep Accepted
  grep "Accepted" /var/log/auth.log 2>/dev/null
} | tail -3 || echo "   (sin datos)"


echo ""
echo "🚨 Últimos fallos:"
{
  sudo journalctl -u ssh --no-pager 2>/dev/null | grep Failed
  grep "Failed" /var/log/auth.log 2>/dev/null
} | tail -3 || echo "   (sin datos)"

} >> "$INFORME"

tail -160 "$INFORME"
# Mantener solo las últimas 500 líneas
tail -1000 ~/logs/accesos-vps.log > ~/logs/accesos-vps.log.tmp
mv ~/logs/accesos-vps.log.tmp ~/logs/accesos-vps.log

echo ""
echo "📋 COMANDOS ÚTILES:"
echo "   sudo fail2ban-client status sshd            → IPs bloqueadas"
echo "   ss -tn state established                    → conexiones activas"
echo "   ss -tn state established  | grep ':22'      → conexiones activas"
echo "   curl -s ipinfo.io/ip                        → mi IP pública"
echo "   curl -s ipinfo.io/IP                        → info de una IP"
echo "   sudo journalctl -u ssh | grep Accepted      → accesos OK"
echo "   sudo journalctl -u ssh | grep Failed        → intentos fallidos"
echo ""

fastfetch

Guardar y salir (nano ctrl+o y ctrl+x)

chmod +x ~/bin/monitor.sh   # Dar permisos
nano ~/.bashrc

Pegar

if [[ -n "$SSH_CONNECTION" ]]; then
    ~/bin/monitor.sh
fi
source ~/.bashrc # Recarga

Guardar y salir (nano ctrl+o y ctrl+x)

sudo visudo

Añadir

web ALL=(ALL) NOPASSWD: /usr/bin/fail2ban-client
web ALL=(ALL) NOPASSWD: /usr/bin/journalctl
web ALL=(root) NOPASSWD: /usr/bin/fail2ban-client status sshd

Guardar y salir (nano ctrl+o y ctrl+x)

which fail2ban-client
/usr/bin/fail2ban-client

Alertas automáticas (sin estar conectado)

nano ~/bin/check-alerts.sh
#!/bin/bash

ALERT=0
MSG=""

# Disco
DISK=$(df / | awk 'NR==2{print $5}' | tr -d '%')
if [ "$DISK" -gt 80 ]; then
  ALERT=1
  MSG+="Disco >80% ($DISK%)\n"
fi

# Memoria
MEM=$(free | awk '/Mem:/ {printf("%.0f"), $3/$2*100}')
if [ "$MEM" -gt 80 ]; then
  ALERT=1
  MSG+="RAM >80% ($MEM%)\n"
fi

# Fail2ban
BANS=$(fail2ban-client status sshd 2>/dev/null | grep "Currently banned" | awk '{print $4}')
if [ "${BANS:-0}" -gt 20 ]; then
  ALERT=1
  MSG+="Muchos bans: $BANS\n"
fi

# SSH brute force
FAILS=$(journalctl -u ssh -S -10min | grep Failed | wc -l)
if [ "$FAILS" -gt 30 ]; then
  ALERT=1
  MSG+="Muchos fallos SSH: $FAILS\n"
fi

if [ "$ALERT" -eq 1 ]; then
  echo -e "$(date)\n$MSG" >> "$HOME/logs/alerts.log"
fi

chmod +x ~/bin/check-alerts.sh # Permisos
crontab -e Automatizar con cron

Añadir:

*/10 * * * * /home/web/bin/check-alerts.sh

Alertas automáticas (recomendado). Si quieres que te avise si algo peta Monit puede:

  • Reiniciar nginx si cae

  • Avisar si disco se llena

  • Avisar si RAM se dispara

    sudo apt install monit # Instala monit sudo systemctl enable --now monit # Actívalo

FASE 23 — Backups

VPS

sudo apt install rsync
# rsync -aAXHv --delete --exclude 'nombre_del_directorio' /origen/ /destino/
mkdir -p ~/web-backup/{snapshots,logs}
nano ~/bin/backup.sh

Pegar

#!/bin/bash

set -euo pipefail

DEST="$HOME/web-backup"
SNAP="$DEST/snapshots"
LOG="$DEST/logs/backup.log"

DATE=$(date +"%Y-%m-%d_%H-%M")
CURRENT="$SNAP/$DATE"

HOME_SNAP="$CURRENT/home"
ETC_SNAP="$CURRENT/etc"
VAR_SNAP="$CURRENT/var"
SYS_SNAP="$CURRENT/system"

LATEST="$SNAP/latest"

mkdir -p \
  "$HOME_SNAP" \
  "$ETC_SNAP" \
  "$VAR_SNAP" \
  "$SYS_SNAP" \
  "$DEST/logs"

exec >> "$LOG" 2>&1

echo "==============================="
echo "🕒 Backup: $DATE"
echo "==============================="

echo ""
echo "📊 Tamaño previo:"

du -sh /home/web/web1.com 2>/dev/null || true
du -sh /home/web/miweb2.com 2>/dev/null || true
du -sh /home/web/miweb5.es 2>/dev/null || true
du -sh /home/web/miweb3.es 2>/dev/null || true
du -sh /home/web/* 2>/dev/null || true
du -sh "$HOME/logs" 2>/dev/null || true
du -sh "$HOME/bin" 2>/dev/null || true
du -sh "$HOME/config" 2>/dev/null || true
du -sh /etc/nginx 2>/dev/null || true
du -sh /var/spool/cron 2>/dev/null || true

echo ""
echo "📦 Guardando paquetes..."

dpkg --get-selections > "$SYS_SNAP/packages.list"
apt-mark showmanual > "$SYS_SNAP/packages-manual.list"
cp /var/log/apt/history.log "$SYS_SNAP/apt-history.log"

echo ""
echo "🚀 Iniciando rsync..."

LINK=""

# if [ -d "$LATEST" ]; then
#   LINK="--link-dest=$LATEST"
#   echo "Usando incremental desde: latest"
# fi
if [ -L "$LATEST" ]; then
  LINK="--link-dest=$(readlink -f "$LATEST")"
  echo "🔗 Incremental desde latest"
fi

# ----------------------------
# HOME / WEBS
# ----------------------------

echo "🌐 Backup HOME"

rsync -aAXHv --delete \
  $LINK \
  --exclude "$DEST" \
  "$HOME/web1.com" \
  "$HOME/miweb2.pm" \
  "$HOME/miweb5.es" \
  "$HOME/miweb3.es" \
  "$HOME/logs" \
  "$HOME/bin" \
  "$HOME/config" \
  "$HOME/.ssh" \
  "$HOME/.bashrc" \
  "$HOME/.profile" \
  "$HOME/.config" \
  "$HOME/.bash_history" \
  "$HOME_SNAP/"


# ----------------------------
# ETC
# ----------------------------

echo "⚙️ Backup /etc"

rsync -aAXHv --delete \
  $LINK \
  /etc/nginx \
  /etc/ssh \
  /etc/fail2ban \
  /etc/nftables.conf \
  /etc/nftables.d \
  /etc/systemd/system \
  /etc/cron* \
  "$ETC_SNAP/"

# ----------------------------
# VAR
# ----------------------------

echo "📂 Backup /var"

rsync -aAXHv --delete \
  $LINK \
  /var/spool/cron \
  "$VAR_SNAP/"


# ----------------------------
# ACTUALIZAR LATEST
# ----------------------------

rm -f "$LATEST"
ln -s "$CURRENT" "$LATEST"
 
# ----------------------------
# ROTACIÓN
# ----------------------------

echo "🧹 Aplicando rotación..."

cd "$SNAP"

ls -1dt 20* | tail -n +15 | while read -r old; do
  echo "🗑️ Eliminando backup antiguo: $old"
  #rm -rf "$old"
done
# ls -1dt 20*   # Lista backups por fecha (más nuevos primero)
# tail -n +15   # Desde el 15 en adelante → viejos
# rm -rf        # Borra

# ----------------------------
# FINAL
# ----------------------------
echo "✅ Backup terminado"
echo ""
echo "📦 Tamaño snapshot:"
du -sh "$CURRENT"
chmod +x ~/bin/backup.sh        # Dar permisos
./backup.sh                 # Ejecutar

Recuperar los apt install:

dpkg --set-selections < packages.list apt-get dselect-upgrade

Nota. Para máxima seguridad con rutas absolutas:

BASE="/home/directorio/copias"

ls -1dt "$BASE"/20* 2>/dev/null | tail -n +15 | while read -r old; do
# Validar que la ruta empieza por BASE y contiene formato de fecha
if [[ "$old" =~ ^$BASE/20[0-9]{6} ]]; then
    echo "🗑️ Eliminando: $old"
    rm -rf "$old"
else
    echo "⚠️  Ruta sospechosa bloqueada: $old"
fi
done

Ventaja brutal de esta versión

# Restaurar solo nginx
rsync -a /home/web/web-backup/snapshots/latest/etc/nginx/ /etc/nginx/

# Restaurar solo webs
rsync -a snapshots/latest/home/web/ /home/web/

# Restaurar cron
rsync -a snapshots/latest/var/spool/cron/ /var/spool/cron/

# Reinstalar sistema entero
dpkg --set-selections < packages.list
apt-get dselect-upgrade

Backup automático con cron

sudo apt update                 # actualizar listas
sudo apt install cron           # instalar
sudo systemctl enable cron      # habilitar
sudo systemctl start cron       # iniciar
systemctl status cron           # verificar que está corriendo deberías ver active (running).

crontab -e                              # Editar cron con nano
0 3 * * * /home/web/bin/backup.sh       # Añadir:  Cada día a las 03:00

~/bin/backup.sh         # Probar ahora
~/bin/monitor.sh            # Luego y mirar la sección BACKUPS

FASE 24 — Cambiar puerto 22 a puerto 22222

firewall del PROVEEDOR DEL VPS

    Abrir puerto 22222

Qué archivos tengo que modificar para cambiar del puerto 22 al puerton 22222 para ssh? Solo tres archivos en el VPS, uno en el LOCAL y el puerto en el firewall del PROVEEDOR DEL VPS:

VPS - SSH

    sudo nano /etc/ssh/sshd_config  
    Port 22
    Port 22222

VPS - Firewall

    sudo nano /etc/nftables.conf      
    tcp dport { 22, 22222, 80, 443 } accept

VPS - fail2ban -

    sudo nano /etc/fail2ban/jail.local
    [sshd]
    enabled = true
    port = 22,22222

Después reinicia los servicios:

sudo systemctl restart sshd
sudo systemctl restart ssh
sudo systemctl restart nftables
sudo systemctl restart fail2ban

sudo systemctl status sshd
q o crtl+c
sudo systemctl status ssh
sudo systemctl status nftables
sudo systemctl status fail2ban


sudo nft -c -f /etc/nftables.conf          # Aplicar
sudo nft list ruleset                   # Verificar

LOCAL

sudo nano ~/.ssh/config

Host vps-web
    HostName IP
    User web
    Port 22222
    IdentityFile ~/.ssh/vps_debian
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host vps-web22
    HostName IP
    User web
    Port 22
    IdentityFile ~/.ssh/vps_debian
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

IMPORTANTE: Antes de cerrar tu sesión actual, abre una segunda terminal y prueba:

sh -p 22222 web@217.76.133.194

Si entra bien, ya puedes cerrar la sesión del puerto 22. Si no entra, tienes la sesión original para arreglar el problema. Cerrar el 22 (solo cuando confirmes que 22222 funciona).

Quita Port 22 de:

sshd_config, 
firewall  
fail2ban

y reinicia los tres servicios de nuevo (igual que el reinicio anterior)

FASE 25 — Reconfigurar Nginx

Headers de seguridad, dentro del server {}

# Seguridad
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=()" always;

# HSTS (solo si SSL funciona bien)
#add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Bloquear archivos peligrosos, añade en cada server:

# Bloquear archivos ocultos
location ~ /\.(?!well-known) {
    deny all;
}

# Bloquear backups
location ~* \.(bak|old|swp|tmp)$ {
    deny all;
}

Forzar HTTPS en cada dominio, así nadie entra por HTTP.:

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

Rate limiting (anti bots), frena crawlers agresivos, dentro http {} en cada server:

limit_req zone=one burst=20 nodelay

Contenido provisional http

sudo cat /etc/nginx/sites-available/web1.com
server {
    listen 80;
    listen [::]:80;

    server_name web1.com www.web1.com;

    root /home/web/web1.com/public;
    index index.html;

    access_log /var/log/nginx/web1.access.log;
    error_log  /var/log/nginx/web1.error.log;

    # Headers de seguridad
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=()" always;

    client_max_body_size 5M;

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

    # Bloquear archivos ocultos
    location ~ /\.(?!well-known) {
        deny all;
    }

    # Bloquear backups
    location ~* \.(bak|old|swp|tmp)$ {
        deny all;
    }
}

Contenido provisional http

sudo cat /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
worker_cpu_affinity auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;

events {
    #worker_connections 768;
    worker_connections 2048;
    # multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    sendfile on;
    tcp_nopush on;
    keepalive_timeout 30;

    types_hash_max_size 2048;
    server_tokens off; # Recommended practice is to turn this off
    
    client_max_body_size 5M;
    
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;	
    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    open_file_cache max=10000 inactive=30s;
        open_file_cache_valid 60s;
        open_file_cache_min_uses 2;
        open_file_cache_errors on;


    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3 (POODLE), TLS 1.0, 1.1
    ssl_prefer_server_ciphers off; # Don't force server cipher order.

    ##
    # Logging Settings
    ##

    access_log /var/log/nginx/access.log;

    ##
    # Gzip Settings
    ##

    gzip on;

    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    
    gzip_min_length 1024;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_types
            text/plain
            text/css
            text/xml
            text/javascript
            application/javascript
            application/json
            application/xml
            application/rss+xml
            image/svg+xml;
    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

sudo chown -R web:www-data /home/web

sudo chmod 755 /home
sudo chmod 750 /home/web
sudo chmod -R 750 /home/web/web1.com
namei -l /home/web/web1.com/public/index.html

Reinicia

sudo nginx -t
sudo systemctl reload nginx