{"id":239645,"date":"2026-05-05T23:06:10","date_gmt":"2026-05-05T23:06:10","guid":{"rendered":"https:\/\/felipenespralsanchez.tech\/blog\/?p=239645"},"modified":"2026-06-13T23:08:48","modified_gmt":"2026-06-13T23:08:48","slug":"lemp-ubuntu-24-04-hardening-tuning","status":"publish","type":"post","link":"https:\/\/felipenespralsanchez.tech\/blog\/lemp-ubuntu-24-04-hardening-tuning\/","title":{"rendered":"LEMP en Ubuntu 24.04: el hardening y el tuning que los tutoriales se saltan"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Mont\u00e9 un servidor nuevo desde cero para mis proyectos personales: un stack LEMP en Ubuntu 24.04, todo por SSH. Hay mil tutoriales de c\u00f3mo instalar Linux, Nginx, MariaDB y PHP, y no voy a escribir el n\u00famero mil uno. Lo que falta en casi todos es el porqu\u00e9 de cada decisi\u00f3n, el hardening de verdad y los detalles que rompen cuando ya cre\u00edas que estaba todo hecho. Eso es lo que cuento aqu\u00ed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El \u00abc\u00f3mo instalar\u00bb lo encuentras en cualquier sitio. El criterio para no dejarlo a medias, no.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Por qu\u00e9 LEMP en 2026 (y por qu\u00e9 Ubuntu 24.04 LTS)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">LEMP en vez de LAMP por una raz\u00f3n concreta: Nginx. Vengo de pelearme con Apache y sus <code>.htaccess<\/code>, y en mi \u00faltimo proyecto ya hab\u00eda movido todo a Nginx. La diferencia que me importa no es solo el rendimiento bajo carga; es que toda la configuraci\u00f3n vive en un sitio, declarada expl\u00edcitamente, en vez de repartida en archivos <code>.htaccess<\/code> que cualquiera puede dejar caer en una carpeta y cambiarte el comportamiento del servidor sin que te enteres. M\u00e1s control, menos sorpresas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eleg\u00ed Ubuntu 24.04 LTS (Noble Numbat) por lo aburrido que es, y lo digo como elogio. Una LTS me da cinco a\u00f1os de actualizaciones de seguridad sin tener que migrar de versi\u00f3n mayor. En un servidor que quiero olvidar y que funcione, esa estabilidad vale m\u00e1s que tener los paquetes m\u00e1s nuevos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El stack de un vistazo: todo del repo, y por qu\u00e9<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Aqu\u00ed va la primera decisi\u00f3n que un tutorial no suele justificar: lo dej\u00e9 todo del repositorio por defecto de Ubuntu. Nginx, MariaDB 10.11 LTS y PHP 8.3-FPM, las versiones que trae 24.04, sin traer nada de upstream.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Es tentador a\u00f1adir el repo de nginx.org para la mainline, o un PPA para la \u00faltima PHP. No lo hice, y a prop\u00f3sito. Cuando dependes de los paquetes de Ubuntu, las actualizaciones de seguridad llegan por el canal oficial: un <code>apt upgrade<\/code> y listo, sin repos de terceros que mantener ni que se queden hu\u00e9rfanos. La contrapartida es no tener la versi\u00f3n m\u00e1s reciente de cada cosa. Para un servidor de producci\u00f3n que quiero estable, ese cambio me compensa siempre.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para PHP instal\u00e9 las extensiones que el stack necesita y nada m\u00e1s: <code>imagick<\/code>, <code>xml<\/code>, <code>mbstring<\/code>, <code>curl<\/code>, <code>zip<\/code>, <code>intl<\/code> y <code>opcache<\/code>. Cada extensi\u00f3n es c\u00f3digo que corre con privilegios; cuantas menos haya, menos superficie.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lo que un tutorial no te cuenta<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La decisi\u00f3n t\u00e9cnica de la que m\u00e1s se habla mal por omisi\u00f3n es c\u00f3mo conecta Nginx con PHP. La mayor\u00eda de tutoriales te dejan PHP-FPM escuchando en TCP (<code>127.0.0.1:9000<\/code>) y nadie explica por qu\u00e9. Yo lo puse en socket Unix (<code>unix:\/var\/run\/php\/php8.3-fpm.sock<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El motivo: en un servidor de una sola m\u00e1quina, donde Nginx y PHP-FPM viven juntos, el socket Unix es m\u00e1s r\u00e1pido. Se comunican directamente a trav\u00e9s del sistema de archivos, sin pasar por la pila TCP\/IP y su overhead. El TCP solo tiene sentido si PHP-FPM corre en una m\u00e1quina separada de Nginx, que no es mi caso. Usar TCP en localhost es pagar un peaje de red para un viaje que no sale de casa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El otro detalle es que un \u00fanico server block sirve dos cosas a la vez: el portfolio en PHP plano en la ra\u00edz y un WordPress que vive en el subdirectorio <code>\/blog\/<\/code>. No necesito dos bloques ni dos dominios; con los <code>location<\/code> correctos, el mismo server reparte el tr\u00e1fico. Aqu\u00ed est\u00e1 el n\u00facleo de la config, con las rutas anonimizadas:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>server {\n    listen 443 ssl http2;\n    server_name example.com;\n\n    root \/var\/www\/site\/public;\n    index index.php index.html;\n\n    location \/ {\n        try_files $uri $uri\/ \/index.php?$args;\n    }\n\n    location \/blog\/ {\n        try_files $uri $uri\/ \/blog\/index.php?$args;\n    }\n\n    location ~ \\.php$ {\n        include snippets\/fastcgi-php.conf;\n        fastcgi_pass unix:\/var\/run\/php\/php8.3-fpm.sock;\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">OPcache lo dej\u00e9 activo desde el principio. Compila el bytecode de PHP una vez y lo cachea en memoria, as\u00ed no se reparsea en cada petici\u00f3n. Para un sitio con tr\u00e1fico real es de las mejoras de rendimiento m\u00e1s baratas que existes: cambias un par de l\u00edneas de config y ya est\u00e1.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Endurecerlo desde el primer d\u00eda<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El hardening no es un paso final que haces cuando \u00abtienes tiempo\u00bb. Es lo primero, porque en cuanto el servidor tiene una IP p\u00fablica empieza a recibir intentos de acceso autom\u00e1ticos. Los bots no esperan a que termines.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Empec\u00e9 por la base de datos. <code>mysql_secure_installation<\/code> no es un tr\u00e1mite de pulsar Enter cinco veces: quit\u00e9 los usuarios an\u00f3nimos, bloque\u00e9 el login remoto de root, elimin\u00e9 la base de datos de prueba y puse una contrase\u00f1a de root seria. Cada uno de esos pasos cierra una puerta que viene abierta por defecto.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El firewall lo gestion\u00e9 con UFW, con una pol\u00edtica de denegar todo y abrir solo lo imprescindible: SSH, HTTP y HTTPS. Nada m\u00e1s expuesto. Si un servicio no necesita estar accesible desde fuera, no lo est\u00e1.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">SSH lo mov\u00ed a un puerto no est\u00e1ndar. Y aqu\u00ed soy honesto sobre lo que esto es y lo que no es: no es seguridad real. Mover SSH del 22 no para a un atacante decidido. Lo que hace es reducir el ruido: la inmensa mayor\u00eda de los escaneos autom\u00e1ticos golpean el 22 y se van, as\u00ed que cambiar el puerto me limpia los logs de basura y me deja ver los intentos que de verdad importan. El blindaje de SSH est\u00e1 en las claves y en deshabilitar el login por contrase\u00f1a, no en el n\u00famero de puerto.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El TLS lo resolv\u00ed con Let&#8217;s Encrypt. Certificado gratis, renovaci\u00f3n autom\u00e1tica, HTTPS desde el d\u00eda uno. En 2026 servir HTTP plano no tiene excusa, y montar esto cuesta diez minutos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Usuarios y permisos: el l\u00edo que de verdad cuesta<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Esta es la parte que ning\u00fan tutorial de \u00abinstala LEMP en cinco minutos\u00bb te cuenta, y es la que m\u00e1s tiempo me llev\u00f3. Tengo varios proyectos en el mismo servidor y quer\u00eda que cada uno se gestionara por separado, sin que el acceso a uno diera acceso a los dem\u00e1s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El esquema: un usuario SSH general para administrar la m\u00e1quina, y luego un usuario FTP separado por cada proyecto. Cada uno entra solo a lo suyo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El problema real aparece en cuanto mezclas eso con PHP. PHP-FPM corre como <code>www-data<\/code>, pero los archivos los sube un usuario FTP que es otra persona distinta. Si no alineas grupos y permisos, te pasa una de dos: o PHP no puede leer lo que el usuario FTP subi\u00f3, o el usuario FTP no puede escribir donde PHP necesita. El servidor parece roto y la causa no salta a la vista, porque no es un error de configuraci\u00f3n de Nginx ni de PHP: es de propiedad de archivos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lo resolv\u00ed ajustando los grupos y los permisos de cada carpeta de proyecto para que el usuario FTP correspondiente y <code>www-data<\/code> pudieran convivir, cada uno escribiendo y leyendo lo que le toca, sin pisarse y sin que un proyecto tuviera acceso al de al lado. Suena simple escrito as\u00ed; en la pr\u00e1ctica es donde se va la tarde si no lo piensas antes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Qu\u00e9 dej\u00e9 listo para producci\u00f3n y qu\u00e9 har\u00eda distinto<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Lo que tengo ahora es un servidor que puedo olvidar: actualizaciones de seguridad por el canal oficial, certificado que se renueva solo, firewall cerrado y cada proyecto en su corral. Para mis proyectos personales es exactamente el nivel que necesito.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lo del socket Unix y OPcache lo repetir\u00eda sin pensarlo. Son decisiones de cero coste y beneficio inmediato que mucha gente se salta solo porque el tutorial que sigui\u00f3 las omit\u00eda.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u00bfQu\u00e9 cambiar\u00eda? El acceso por FTP. Funciona, pero FTP es de otra \u00e9poca; un <code>sftp<\/code> sobre la misma conexi\u00f3n SSH ser\u00eda m\u00e1s limpio y una cosa menos que endurecer por separado. No es urgente, pero es lo primero que tocar\u00eda en la pr\u00f3xima vuelta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si te llevas una idea de aqu\u00ed, que sea esta: instalar el stack es la parte f\u00e1cil y resuelta. El trabajo de verdad est\u00e1 en las decisiones que nadie copia y pega \u2014el socket, los permisos, qu\u00e9 cierras y qu\u00e9 dejas abierto\u2014. Ah\u00ed es donde un servidor pasa de \u00abarranca\u00bb a \u00ablo dejo en producci\u00f3n y duermo tranquilo\u00bb.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Desplegu\u00e9 un stack LEMP en Ubuntu 24.04 desde cero para mis proyectos. No es otro paso a paso: cuento las decisiones que importan y lo no obvio. Por qu\u00e9 dej\u00e9 todo del repo, PHP-FPM por socket Unix en vez de TCP, el hardening desde el primer d\u00eda (UFW, TLS, SSH en otro puerto) y el l\u00edo real de permisos entre los usuarios FTP y www-data.<\/p>\n","protected":false},"author":1,"featured_media":239646,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,27],"tags":[180,178,49,176,174],"class_list":["post-239645","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","category-desarrollo-web","tag-linux","tag-mariadb","tag-nginx","tag-php-fpm","tag-ubuntu"],"_links":{"self":[{"href":"https:\/\/felipenespralsanchez.tech\/blog\/wp-json\/wp\/v2\/posts\/239645","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/felipenespralsanchez.tech\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/felipenespralsanchez.tech\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/felipenespralsanchez.tech\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/felipenespralsanchez.tech\/blog\/wp-json\/wp\/v2\/comments?post=239645"}],"version-history":[{"count":1,"href":"https:\/\/felipenespralsanchez.tech\/blog\/wp-json\/wp\/v2\/posts\/239645\/revisions"}],"predecessor-version":[{"id":239647,"href":"https:\/\/felipenespralsanchez.tech\/blog\/wp-json\/wp\/v2\/posts\/239645\/revisions\/239647"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/felipenespralsanchez.tech\/blog\/wp-json\/wp\/v2\/media\/239646"}],"wp:attachment":[{"href":"https:\/\/felipenespralsanchez.tech\/blog\/wp-json\/wp\/v2\/media?parent=239645"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/felipenespralsanchez.tech\/blog\/wp-json\/wp\/v2\/categories?post=239645"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/felipenespralsanchez.tech\/blog\/wp-json\/wp\/v2\/tags?post=239645"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}