Autor: Joel Barrios Dueñas
Correo electrónico: darkshram en gmail punto com
Sitio de Red: https://www.alcancelibre.org
Licencia Creative Commons
© 1999-2026 Joel Barrios Dueñas. Este manual se distribuye bajo la licencia Creative Commons Reconocimiento-NoComercial-CompartirIgual 4.0 Internacional (CC BY-NC-SA 4.0). Usted es libre de compartir y adaptar el material bajo los siguientes términos: debe dar crédito al autor, no puede utilizarlo para fines comerciales y debe compartir las obras derivadas bajo la misma licencia. La licencia completa está disponible en https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.es.
Wordpress es sin duda alguna el CMS más popular del mundo. Fue publicado por primera vez el 27 de mayo de 2003 con el enfoque de servir como sistema gestor de contenidos para cualquier tipo de sitio Web. Es software libre publicado bajo los términos de la licencia GNU/GPL versión 2.0. Está desarrollado en el lenguaje PHP para entornos que ejecuten MySQL o MariaDB como motores de bases de datos y Apache o Nginx como servidor Web.
Buena parte del éxito y adopción de Wordpress como CMS es la enorme comunidad de desarrolladores y diseñadores y el enorme ecosistema de complementos y plantillas libres y comerciales. Y es por todo ésto que dos terceras partes de todos los sitios de Web actuales utilizan Wordpress. Al tratarse de un software tan popular, es obvio que también es uno de los principales objetivos de la delincuencia informática. Por tanto instalarlo de manera segura es importante para cualquier administrador de sistemas.
⚠️ Enfoque de seguridad: Este manual enfatiza prácticas de seguridad como el uso de un usuario sin privilegios, la selección cuidadosa de la versión de PHP, la protección del directorio de administración y la integración con herramientas como Fail2Ban. Siga estas recomendaciones para reducir significativamente la superficie de ataque de su instalación.
La elección de la versión de PHP es una decisión crítica que equilibra rendimiento, seguridad y compatibilidad.
PHP 7.4: Se menciona en este manual porque, si bien Wordpress funciona con versiones modernas de PHP, el ecosistema de extensiones (plugins) es extenso y heterogéneo. Desafortunadamente, cientos de extensiones abandonadas por sus creadores siguen en uso por diseñadores por costumbre, inercia o falta de alternativas inmediatas, y su compatibilidad suele limitarse a PHP 7.4. Sólo debe elegirse esta versión si se tiene la certeza de que todos los complementos necesarios son incompatibles con PHP 8.0 o superiores.
PHP 8.0+ (Recomendado): Las versiones modernas de PHP (8.0, 8.1, 8.2, 8.3) ofrecen mejoras de rendimiento significativas y correcciones de seguridad críticas. Siempre que sea posible, se debe preferir la versión más reciente y estable soportada por su distribución. Antes de migrar, verifique la compatibilidad de todos los plugins y temas que planea utilizar.
Procedimiento para instalar una versión específica de PHP en AlmaLinux/Rocky Linux 8/9:
# Habilitar el módulo de la versión deseada (ej: PHP 8.2)
dnf module enable php:8.2
# Instalar PHP-FPM y extensiones comunes para la versión habilitada
dnf -y install php-fpm php-bcmath php-dba php-enchant php-gd php-gmp php-intl \
php-json php-mbstring php-mysqlnd php-opcache php-pdo php-pdo-dblib \
php-process php-pspell php-soap php-sodium php-tidy php-xml php-xmlrpc
Wordpress puede funcionar con la configuración estándar de PHP y una configuración sencilla de Nginx. Se utilizará el puerto 8014 ―como ejemplo hipotético― para la instancia de PHP-FPM. Se utilizará un usuario sin privilegios denominado nombreyapellido ―como ejemplo― y su directorio de inicio correspondiente para la configuración del anfitrión virtual. El sitio Web con Wordpress se accederá desde el navegador Web como https://nombreyapellido.net o bien https://www.nombreyapellido.net―como ejemplo.
Es indispensable que el nombre de anfitrión utilizado ―en el ejemplo de este documento será https://nombreyapellido.net o https://www.nombreyapellido.net― esté resuelto en la zona del dominio correspondiente en un servidor DNS ― si necesita que el sitio Web esté disponible desde cualquier ubicación― o bien al menos en el archivo /etc/hosts ― que sólo estará disponible desde el mismo sistema― ya que de otro modo será imposible acceder al instalador desde el navegador Web.
Configuración de DNS para entornos educativos: Para clases o laboratorios donde los sistemas carezcan de acceso a DNS públicos, resulta muy práctico configurar un servidor DNS interno que permita a todos los estudiantes resolver nombres de dominio locales. DNSMasq es una solución ligera ideal para este propósito, ya que funciona tanto como servidor DNS y DHCP para redes pequeñas. Si desea implementar esta solución, consulte el manual Instalación y configuración de DNSMasq que explica cómo configurar DNSMasq para servir nombres de dominio locales y redirigir consultas externas, permitiendo que todos los sistemas en la red del aula resuelvan nombreyapellido.net y accedan al sitio de práctica sin configuraciones manuales en /etc/hosts.
Acceda al sistema como usuario root. Ejecute lo siguiente para instalar todo el software requerido para los procedimientos descritos en este documento:
dnf -y install wget2 nginx php-fpm php-bcmath php-dba php-enchant php-gd \
php-gmp php-intl php-json php-mbstring php-mysqlnd php-opcache php-pdo \
php-pdo-dblib php-process php-pspell php-soap php-sodium php-tidy php-xml \
php-xmlrpc
Si aún le falta hacerlo, active e inicie los servicios nginx y php-fpm:
systemctl enable --now php-fpm
systemctl enable --now nginx
Importancia: Utilizar un usuario independiente sin privilegios para cada sitio Web es una práctica de seguridad fundamental. Si un sitio es comprometido, el atacante sólo tendrá acceso a los archivos bajo el directorio /home de ese usuario, limitando el daño. Si en cambio se utiliza el usuario apache o nginx, un ataque exitoso comprometería todos los sitios y recursos propiedad de ese usuario de sistema.
Genere el usuario nombreyapellido:
useradd -c "Nombre y Apellido" nombreyapellido
passwd nombreyapellido
Genere el directorio /home/nombreyapellido/public_html y otros directorios que serán de utilidad para el anfitrión virtual:
mkdir -p \
/home/nombreyapellido/{public_html,private,uploads,tmp,.config,.local,.ssh}
Asigne pertenencia a estos directorios para el usuario y grupo nombreyapellido:
chown nombreyapellido:nombreyapellido \
/home/nombreyapellido/{public_html,private,uploads,tmp,.config,.local,.ssh}
Revisión de contexto SELinux: El contexto httpd_sys_rw_content_t es necesario para directorios donde PHP-FPM (ejecutándose como el usuario nombreyapellido) deba escribir archivos, como ~/tmp o ~/public_html/wp-content. Para el directorio ~/public_html en sí, generalmente es suficiente con el contexto predeterminado que asigna restorecon. Aplique los contextos específicos sólo donde sea estrictamente necesario:
semanage fcontext -a -t httpd_sys_rw_content_t "/home/nombreyapellido/tmp"
semanage fcontext -a -t httpd_sys_rw_content_t "/home/nombreyapellido/tmp(/.*)?"
semanage fcontext -a -t httpd_sys_rw_content_t "/home/nombreyapellido/public_html/wp-content"
semanage fcontext -a -t httpd_sys_rw_content_t "/home/nombreyapellido/public_html/wp-content(/.*)?"
Cambie los permisos de /home/nombreyapellido a fin de permitir que Nginx pueda acceder hacia el directorio public_html y el resto de los directorios creados:
chmod 755 /home/nombreyapellido
Restaure los contextos de SELinux para todo el contenido de /home/nombreyapellido:
restorecon -Rv /home/nombreyapellido
Seguridad de acceso por SFTP (Recomendación avanzada): Para un acceso aún más seguro, puede configurar OpenSSH para permitir sólo SFTP con jaula (chroot) para este usuario. Esto implica cambiar la pertenencia del directorio /home/nombreyapellido a root, cambiar el intérprete de mandatos del usuario a /usr/sbin/nologin y añadirlo al grupo sftpusers. Este método, detallado en el manual de Configuración de OpenSSH, garantiza que el usuario sólo pueda transferir archivos vía SFTP (con clientes como Filezilla) y jamás obtenga un intérprete de mandatos interactivo.
chown root:root /home/nombreyapellido
usermod -s /usr/sbin/nologin -G sftpusers nombreyapellido
La base de datos para el ejemplo de este documento se denominará dbnombreyapellido. Para las credenciales de acceso de esta base de datos se utilizará el usuario wpnombreyapellido con la contraseña Us3Un4Bu3n4C0ntr4s3ñ4.
Acceda como root al intérprete de MySQL o MariaDB:
mysql -uroot -p
Proceda a crear una nueva base de datos con un nombre que pueda ser asociado al usuario. Mientras menos predecible sea el nombre de la base de datos, mejor.
create database dbnombreyapellido;
Conceda permisos de acceso a esta nueva base de datos a un usuario con contraseña:
grant all on dbnombreyapellido.* to wpnombreyapellido@localhost identified by 'Us3Un4Bu3n4C0ntr4s3ñ4';
Refresque los privilegios y salga de el intérprete de MySQL o MariaDB:
flush privileges;
exit;
Hay tres archivos que se recomienda crear. Se asume que ha leído, estudiado y practicado los procedimientos del documento titulado Configuración de Nginx, disponible en la sección de manuales de AlcanceLibre.org para entender el objetivos de éstos.
Genere o edite un archivo denominado /etc/nginx/default.d/common-configs.conf:
vim /etc/nginx/default.d/common-configs.conf
Añada o valide esté presente el siguiente contenido:
# Ocultar la versión de Nginx.
server_tokens off;
# Denegar el acceso a cualquier archivo .ht* o .git*, incluso si
# Nginx jamás los utiliza.
location ~/\.(ht|git) {
deny all;
}
# Establecer el tipo mime predeterminado para el directorio
# /.well-known
location '/.well-known' {
default_type "text/plain";
}
# Indicar a los servidores cache que este tipo de contenidos
# expiran a los 14 días
location ~* \.(js|css|png|jpg|jpeg|gif|swf|xml|txt)$ {
expires 14d;
}
# En caso de omitir un archivo favicon.ico, indicar a Nginx que
# evite registrar la actividad relacionada con este archivo y
# además indicar a los servidores cache que este archivo expira
# tras un año.
location = /favicon.ico {
log_not_found off;
access_log off;
expires 1y;
}
# En caso de omitir un archivo robots.txt, indicar a Nginx que
# evite registrar la actividad relacionada con este archivo.
location = /robots.txt {
log_not_found off;
access_log off;
}
Genere o edite un archivo denominado /etc/nginx/default.d/gzip.conf:
vim /etc/nginx/default.d/gzip.conf
Añada o valide esté presente el siguiente contenido:
# Activar el soporte de compresión gzip
gzip on;
# Activar para archivos que tengan o carezcan de compresión gzip
gzip_vary on;
# Comprimir sólo archivos de al menos 1024 bytes. Con archivos
# menores hay pocos beneficios y elevaría innecesariamente
# el uso del CPU y memoria.
gzip_min_length 1024;
# Activar compresión para conexiones a través de proxy
gzip_proxied expired no-cache no-store private auth;
# Tipos específicos de archivos a comprimir
gzip_types text/plain text/css text/xml text/javascript application/x-javascript
application/xml;
# Deshabilitar para MS Internet Explorer
gzip_disable "MSIE [1-6]\.";
Genere o edite un archivo denominado /etc/nginx/default.d/headers.conf:
vim /etc/nginx/default.d/headers.conf
Añada o valide esté presente el siguiente contenido:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy: "default-src 'self' https://code.jquery.com https://cdnjs.cloudflare.com https://analytics.google.com https://fonts.gstatic.com";
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
Nginx requiere la siguiente configuración para location / en el anfitrión virtual:
# Explicación de la inclusión de los siguientes tres archivos en
# https://www.alcancelibre.org/manuales/configuracion-de-nginx
include /etc/nginx/default.d/common-configs.conf;
include /etc/nginx/default.d/gzip.conf;
include /etc/nginx/default.d/headers.conf;
location / {
index index.html index.htm index.php;
# Esto es indispensable para el soporte de `URLs bonitos`
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:8014;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PHP_VALUE "memory_limit=128M \n upload_max_filesize=128M \n post_max_size=128M;"
}
De modo que la configuración del anfitrión virtual sea similar a lo siguiente:
# Este es un ejemplo de configuración de Nginx para Wordpress
server {
listen 443 http2 ssl;
server_name nombreyapellido.net www.nombreyapellido.net;
root /home/nombreyapellido/public_html;
access_log /var/log/nginx/nombreyapellido-access_log main;
error_log /var/log/nginx/nombreyapellido-error_log notice;
ssl_certificate /ruta/del/archivo/del/certificado.crt;
ssl_certificate_key /ruta/del/archivo/de/firma.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Se recomienda que client_max_body_size tenga el mismo valor que
# memory_limit, post_max_size y upload_max_filesize en la instancia
# de PHP-FPM.
client_max_body_size 128M;
# Explicación de la inclusión de los siguientes tres archivos en
# https://www.alcancelibre.org/manuales/configuracion-de-nginx
include /etc/nginx/default.d/common-configs.conf;
include /etc/nginx/default.d/gzip.conf;
include /etc/nginx/default.d/headers.conf;
location / {
index index.html index.htm index.php;
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:8014;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Se recomienda que memory_limit, post_max_size y
# y upload_max_filesize tengan el mismo valor que
# client_max_body_size.
fastcgi_param PHP_VALUE "memory_limit=128M \n upload_max_filesize=128M \n post_max_size=128M";
}
}
Genere un archivo de configuración para el anfitrión virtual dentro de /etc/nginx/conf.d/ o bien modifique la configuración de un anfitrión virtual existente:
vim /etc/nginx/conf.d/nombreyapellido.conf
Valide que la configuración realizada sea válida:
nginx -t
Recargue servicio nginx para aplicar los cambios:
systemctl reload nginx
Asumiendo que puede utilizar el puerto 8014 para la instancia de PHP-FPM, ejecute lo siguiente para autorizar en SELinux que PHP-FPM pueda utilizar el puerto 8014:
semanage port -a -t http_port_t -p tcp 8014
Genere un nuevo archivo de configuración dentro de /etc/php-fpm.d/ denominado nombreyapellido.conf:
vim /etc/php-fpm.d/nombreyapellido.conf
Añada la siguiente configuración recomendada para php-fpm. Por favor, modifique a su conveniencia.
[nombreyapellido]
user = nombreyapellido
group = nombreyapellido
listen = 127.0.0.1:8014
listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 9999
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 5
php_admin_value[upload_tmp_dir] = /home/nombreyapellido/tmp
php_admin_value[session.save_path] = /home/nombreyapellido/tmp
; Ocultar la versión de PHP
php_value[expose_php] = Off
; Defina la zona horaria que le corresponda.
php_value[date.timezone] = America/Mexico_City
php_value[file_uploads] = On
; Recomendaciones para mejor funcionamiento.
php_value[max_execution_time] = 7200
php_value[request_terminate_timeout] = 300
php_value[max_input_time] = 180
php_value[max_input_vars] = 2000
php_value[output_buffering] = off
Reinicie el servicio php-fpm para aplicar los cambios:
systemctl restart php-fpm
En este manual se opta por configurar PHP-FPM para que escuche en un puerto TCP (ej: 127.0.0.1:8014) en lugar de utilizar un zócalo Unix (socket de archivo). Esta decisión tiene un propósito pedagógico y práctico, especialmente relevante en entornos con SELinux activado y para fines educativos.
Ventajas del enfoque con puerto TCP:
httpd_t) se conecten a puertos TCP dentro de rangos específicos. Los puertos del 8000 al 8999 suelen estar pre-configurados con el contexto http_port_t. Por tanto, sólo es necesario ejecutar un mandato semanage port -a para añadir un puerto específico como el 8014, evitando gestionar contextos complejos en rutas del sistema de archivos.localhost) es sencilla de rastrear y verificar con herramientas comunes como ss -tlnp o netstat. Resulta más intuitiva para estudiantes que están aprendiendo a interconectar servicios./run/php-fpm/nombreyapellido.sock) permitan al mismo tiempo que el usuario de PHP-FPM (nombreyapellido) lo cree y que el proceso de Nginx (normalmente nginx) pueda escribir y leer en él. Un error en estos ajustes provoca el error "Permission denied" o "Bad Gateway". El uso de un puerto TCP elimina por completo esta capa de complejidad.Consideraciones sobre el zócalo Unix:
Si bien es cierto que un zócalo Unix puede ofrecer una latencia ligeramente menor al eliminar la sobrecarga del protocolo de red, esta diferencia es imperceptible en la gran mayoría de las instalaciones y contextos de aprendizaje. La ganancia en simplicidad y claridad operativa del puerto TCP dista de ser un buen método para fines educativos y configuraciones estándar.
Conclusión para el aula:
Para un laboratorio o servidor de producción bajo control estricto, elegir entre un puerto o un zócalo es una decisión de ajuste fino. Sin embargo, en un contexto pedagógico donde el objetivo es que los estudiantes comprendan el flujo entre Nginx y PHP-FPM sin obstáculos, utilizar un puerto TCP es la opción más directa y libre de complicaciones. Se recomienda ceñirse al rango 8001-8999 y documentar el puerto asignado a cada proyecto para mantener el orden.
Acceda como usuario regular:
su -l nombreyapellido -s /bin/bash
Descargue el paquete de la última versión de Wordpress en español. Puede utilizar el archivo comprimido en formato .tar.gz o .zip. Ambos enlaces apuntan a la versión más reciente:
# Opción 1: Descargar en formato .tar.gz
wget2 -P ~/uploads https://es.wordpress.org/latest-es_ES.tar.gz
# Opción 2: Descargar en formato .zip
wget2 -P ~/uploads https://es.wordpress.org/latest-es_ES.zip
Descomprima el archivo descargado dentro de ~/uploads. Si descargó el archivo .tar.gz:
tar zxvf ~/uploads/latest-es_ES.tar.gz -C ~/uploads
Si descargó el archivo .zip:
unzip ~/uploads/latest-es_ES.zip -d ~/uploads
Mueva el contenido de ~/uploads/wordpress directamente dentro de ~/public_html:
mv ~/uploads/wordpress/* ~/public_html/
Restaure los contextos de SELinux:
restorecon -Rv ~/public_html/
Copie el archivo de ejemplo wp-config-sample.php como wp-config.php:
cp -a ~/public_html/wp-config-sample.php ~/public_html/wp-config.php
Acceda hacia https://api.wordpress.org/secret-key/1.1/salt/ para obtener un juego único y aleatorio de firmas digitales. Copie dichos datos.
Edite el archivo /home/nombreyapellido/public_html/wp-config.php:
vim ~/public_html/wp-config.php
Configure los siguientes parámetros de wp-config.php (los valores mostrados son ejemplos, utilice los obtenidos del enlace anterior):
define('DB_NAME', 'dbnombreyapellido');
define('DB_USER', 'wpnombreyapellido');
define('DB_PASSWORD', 'Us3Un4Bu3n4C0ntr4s3ñ4');
define('DB_HOST', 'localhost');
define('DB_CHARSET', 'utf8mb4');
define('DB_COLLATE', '');
// Pegue aquí las líneas de claves únicas generadas.
define('AUTH_KEY', '...');
define('SECURE_AUTH_KEY', '...');
define('LOGGED_IN_KEY', '...');
define('NONCE_KEY', '...');
define('AUTH_SALT', '...');
define('SECURE_AUTH_SALT', '...');
define('LOGGED_IN_SALT', '...');
define('NONCE_SALT', '...');
Acceda desde su navegador Web a https://nombreyapellido.net (o la dirección que haya configurado). Siga los pasos del instalador gráfico de Wordpress.
Tras acceder a la dirección de su sitio en el navegador, se cargará el asistente gráfico de instalación de WordPress. La siguiente imagen corresponde a la pantalla inicial de este proceso:

Figura 1: Pantalla inicial del instalador gráfico de WordPress.
Siga las instrucciones en pantalla para seleccionar el idioma, verificar la configuración de la base de datos y crear la primera cuenta de administrador del sitio.
Tras la instalación básica, implementar capas de protección adicionales es crucial para defender el sitio de amenazas comunes. Las siguientes recomendaciones abordan aspectos específicos que reducen significativamente el riesgo de compromiso.
Wordpress es el principal objetivo de la mayoría de las redes de bots que buscan sitios vulnerables. Es imperativo mantener el núcleo de Wordpress, los temas y todos los complementos actualizados a sus últimas versiones estables. Habilite las actualizaciones automáticas para el núcleo desde el panel de administración (Dashboard → Updates), y revise periódicamente la compatibilidad de los complementos.
Cambiar la ruta predeterminada /wp-admin dificulta los ataques de fuerza bruta automatizados. Puede lograrse mediante un complemento de seguridad o añadiendo la siguiente línea al archivo wp-config.php antes de la línea que dice /* That's all, stop editing! Happy publishing. */:
define('WP_ADMIN_DIR', 'nuevo-directorio-secreto'); // Ej: 'mi-panel-2025'
define('ADMIN_COOKIE_PATH', SITECOOKIEPATH . WP_ADMIN_DIR);
Luego, debe renombrar físicamente el directorio wp-admin en el sistema de archivos para que coincida con el nombre definido. Realice esta operación con extremo cuidado y asegúrese de tener un respaldo completo.
Fail2Ban puede supervisar los registros de Nginx o Wordpress para bloquear direcciones IP que realicen múltiples intentos fallidos de acceso. Los ataques distribuidos modernos utilizan redes de bots que intentan sólo unos pocos accesos por hora desde cada IP, evadiendo configuraciones débiles. Una configuración robusta debe usar una ventana de tiempo amplia y un umbral bajo.
Consulte el manual de Configuración de Fail2Ban para la instalación y configuración base. Luego, puede crear un filtro y una acción específicos para Wordpress. Ejemplo de un filtro simple para bloquear IPs tras 5 intentos fallidos de acceso al wp-login.php en 12 horas (/etc/fail2ban/filter.d/wordpress.conf):
[Definition]
failregex = ^<HOST>.*"POST /wp-login.php HTTP.* 200
ignoreregex =
Y la correspondiente acción en la cárcel (/etc/fail2ban/jail.local):
[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/log/nginx/nombreyapellido-access_log
maxretry = 5
findtime = 43200 ; 12 horas
bantime = 604800 ; 1 semana
Implemente una estrategia de respaldos automatizados que incluya tanto la base de datos como los archivos del sitio. Puede utilizar guiones de instrucciones (scripts) que combinen mysqldump y tar, programados con cron. Almacene los respaldos en un lugar seguro y fuera del servidor.
Siguiendo este manual, habrá instalado Wordpress en un entorno Nginx + PHP-FPM con varias capas de seguridad aplicadas. Recuerde que la seguridad es un proceso continuo: mantenga todo el software actualizado, supervise los registros y ajuste las configuraciones según la evolución de las amenazas.
Para profundizar en la administración de servicios, consulte el manual de Gestión de Servicios con SysVinit (para ALDOS) o Introducción a SystemD (para distribuciones modernas).