Hamdy Tabsissi
ES

Este sitio web En producción Proyecto personal

Un sistema completo que administro, con pruebas verificables en vivo

El sitio que estás leyendo funciona en un servidor que yo mismo configuro, protejo, actualizo y superviso, desde la máquina vacía hasta esta página.

La necesidad

Un portfolio debería demostrar competencias, no solo enumerarlas. Alojarlo en una plataforma gestionada ocultaría precisamente lo que quiero mostrar: mantener un servicio fiable y seguro a lo largo del tiempo.

La idea

Convertir el propio alojamiento en la demostración: todo lo que se cuenta aquí se puede comprobar en directo, en la misma máquina que sirve esta página.

Lo que construí

  • Un servidor en la nube en la región de París, conseguido tras seis intentos a lo largo de varios días: la capacidad gratuita allí escasea.
  • Toda la pila web, con cada servicio en su propio espacio restringido para que un problema no pueda propagarse a los demás.
  • Refuerzo de la seguridad desde la primera noche —7 medidas aplicadas en una sola noche sin ninguna interrupción— y continuado paso a paso.
  • Actualizaciones automáticas: un cambio aprobado se publica solo, se comprueba su estado y se revierte automáticamente si la nueva versión no responde.
  • Copias de seguridad continuas de la base de datos, además de instantáneas nocturnas conservadas a diario, semanal y mensualmente.
  • Una sonda independiente que comprueba la seguridad del sitio cada hora y publica el resultado: puedes verlo más abajo.

El resultado

  • 5comprobaciones de seguridad repetidas cada hora por una sonda independiente
  • 7aplicadas en una noche, sin interrupción
  • 6intentos para conseguir el servidor
  • 1 hentre dos comprobaciones de seguridad independientes

Sirve este portfolio desde agosto de 2026, junto con EventMap. Los controles en directo que aparecen más abajo los mide una sonda independiente; no están escritos a mano.

Controles en directo

  • TLS verificado
  • origen verificado
  • cabeceras verificado
  • DNSSEC verificado
  • disponibilidad verificado

Una sonda independiente mide estos controles cada hora a través de la interfaz pública del sitio: no están escritos a mano. Última ejecución: . Los cinco controles, en detalle (en francés)

En imágenes

Página de inicio de este sitio: «Convierto necesidades en sistemas útiles», sobre un fondo estrellado con un pequeño sistema en órbita
Página de inicio de este sitio: «Convierto necesidades en sistemas útiles», sobre un fondo estrellado con un pequeño sistema en órbita

Lo que aprendí

No probar nunca solo desde la propia máquina: con dos cortafuegos superpuestos, un servicio puede responder perfectamente en local mientras nadie en Internet puede alcanzarlo. Y decidir no instalar algo —aquí, un servidor de bases de datos— también es una competencia de operaciones.

Detalles técnicos Tecnologías, arquitectura y decisiones, para lectores técnicos

Tecnologías

  • Ubuntu 22.04
  • Oracle Cloud ARM (Always Free)
  • nginx
  • Node.js + Express
  • SQLite (node:sqlite)
  • systemd
  • fail2ban
  • iptables
  • Cloudflare
  • GitHub Actions
  • Litestream + restic

La arquitectura de un vistazo

  1. El navegador del visitante
  2. Cloudflare — TLS y caché
  3. nginx — cabeceras de seguridad, limitación de peticiones
  4. Node.js + Express — páginas generadas en el servidor, en 7 idiomas; un servicio systemd aislado
  5. SQLite (node:sqlite) — proyectos y registro de visitas propio, sin terceros
  6. Alrededor: GitHub Actions prueba cada cambio; la VM descarga la versión validada, comprueba /health y vuelve atrás si algo falla; el Observatorio de seguridad repite sus comprobaciones cada 30 minutos.

Cómo funciona

  • Máquina virtual de 4 OCPU / 24 GB (Ubuntu 22.04). Proxy inverso nginx delante de servicios aislados, cada uno con un usuario de sistema dedicado sin shell, con ProtectSystem=strict y NoNewPrivileges.
  • Primera noche (23 de agosto de 2026): contraseña de administración regenerada, fail2ban en SSH, cabeceras de seguridad estrictas (CSP, HSTS, X-Frame-Options), último octeto de la IP a cero en los registros de acceso, retención de journald acotada, rotación de registros de nginx y copia cifrada de los secretos guardada fuera de la máquina.
  • Después: limitación de peticiones en nginx, rpcbind desactivado, swap, escritorio remoto limitado a 127.0.0.1 (solo por túnel SSH), autenticación SSH por contraseña desactivada y certificado Cloudflare Origin CA en modo Full (strict).
  • Despliegue continuo en modo pull: un push a master se valida en GitHub Actions, que mueve una etiqueta prod; la máquina la descarga mediante un temporizador systemd, comprueba su estado y vuelve atrás automáticamente. Ningún puerto de entrada abierto y ninguna credencial de la máquina almacenada en GitHub.
  • Dos cortafuegos superpuestos (iptables en la máquina + la Security List de la nube): por eso nunca pruebo solo desde la propia máquina.
  • Compromiso deliberado: las actualizaciones de seguridad se instalan solas, pero la máquina nunca se reinicia ni cambia de versión mayor por su cuenta.
  • Solo seguimiento de visitas propio: ninguna cookie de terceros, direcciones IP cifradas con hash antes de guardarse y registros eliminados a los 12 meses.

Decisiones y compromisos

  • Páginas generadas en el servidor en lugar de cargadas en el navegador: siguen siendo indexables en siete idiomas y legibles sin JavaScript.
  • Servicios systemd en lugar de Docker: la producción funciona como unidades systemd aisladas, con cuotas de CPU y memoria; un contenedor que nunca se despliega no demostraría nada.
  • Un único flujo JSON público (/api/contenus.json), reutilizado por Mindmap, en lugar de una segunda API: una sola fuente de verdad y nada que sincronizar.

Incidentes y soluciones

  • Tras el rediseño, el informe automático de las pull requests superó el límite de 65.536 caracteres de los comentarios de GitHub (por las capturas incrustadas): las pruebas pasaban, pero la publicación fallaba. Se resolvió con miniaturas más ligeras y un presupuesto de 60.000 caracteres.
  • Cloudflare seguía sirviendo scripts antiguos hasta 4 horas después de un despliegue: ahora cada URL de CSS y JavaScript lleva una huella de su contenido (?v=), así que una versión nueva nunca se mezcla con archivos antiguos.
  • En árabe, un valor como «43 → 9.9 MB» se mostraba al revés por culpa del algoritmo de texto bidireccional: ahora los valores latinos se aíslan con marcas Unicode.

Lo que aún falta

  • Sin reinicio automático: una decisión deliberada