Tutoriales de VPS
Instalar WordPress en un VPS (2026): Docker, MariaDB, HTTPS con Caddy, copias de seguridad y endurecimiento de seguridad
Crea desde cero un WordPress que funcione a largo plazo en un VPS: despliega WordPress y MariaDB con Docker Compose, configura HTTPS automático con Caddy y gestiona por completo el correo SMTP, las copias de seguridad de base de datos y archivos, la reversión de actualizaciones, el endurecimiento de seguridad y los fallos habituales.

Instalar WordPress en un VPS (2026): Docker, MariaDB, HTTPS con Caddy, copias de seguridad y endurecimiento de seguridad
Si quieres un blog, una web corporativa o un sitio independiente de contenidos, WordPress sigue siendo una de las opciones más habituales en un VPS y su demanda de búsqueda sigue siendo una de las más estables. Lo que realmente mantiene un sitio funcionando de forma estable a largo plazo no es abrir el asistente de instalación, sino configurar de una vez la persistencia de la base de datos, HTTPS, las actualizaciones, las copias de seguridad, el correo y los límites de seguridad.
Este artículo usa Ubuntu 24.04 LTS, Docker Compose, la imagen oficial de WordPress, MariaDB y Caddy como ejemplo para desplegar desde cero un entorno de producción de un solo sitio. El ejemplo coloca WordPress y la base de datos en la red interna de Docker, la aplicación escucha solo en un puerto local y Caddy se encarga del HTTPS público.
La configuración de WordPress depende del tema, los plugins, las imágenes y la concurrencia. No compres una máquina solo pensando en "que se pueda instalar":
| Caso de uso | Configuración recomendada | Notas |
|---|---|---|
| Blog personal, web corporativa de presentación | 2 núcleos, 4 GB, 50 GB SSD | Adecuado para pocos plugins y tráfico bajo o medio |
| WooCommerce o sitio de contenidos | 4 núcleos, 8 GB, 100 GB NVMe | Deja margen para PHP, MariaDB y la caché |
| Multisitio o tráfico alto | 4-8 núcleos, 8-16 GB como mínimo | Requiere pruebas de carga, almacenamiento de objetos y planificación de una base de datos independiente |
Prepara un dominio con antelación, por ejemplo www.example.com, y apunta los registros A / AAAA al VPS. El firewall solo debe abrir SSH, 80 y 443; no expongas el puerto 3306 de MariaDB a internet.
Instala Docker Engine y Compose v2 desde el repositorio oficial de Docker y confirma que los comandos están disponibles:
docker --version
docker compose version
Crea un directorio dedicado y restringe los permisos de los archivos de configuración:
sudo mkdir -p /opt/wordpress/backups
sudo chown -R "$USER":"$USER" /opt/wordpress
cd /opt/wordpress
Genera una contraseña de base de datos:
openssl rand -hex 32
openssl rand -hex 32
nano .env
chmod 600 .env
Escribe /opt/wordpress/.env:
MYSQL_ROOT_PASSWORD=替换为第一组随机值
MYSQL_PASSWORD=替换为第二组随机值
WORDPRESS_DB_NAME=wordpress
WORDPRESS_DB_USER=wordpress
WORDPRESS_DB_HOST=db:3306
WORDPRESS_DEBUG=0
No hagas commit de .env en Git ni expongas la contraseña en chats, tickets o capturas de pantalla.
Crea /opt/wordpress/compose.yaml:
services:
db:
image: mariadb:11.4
container_name: wordpress-db
restart: unless-stopped
env_file:
- .env
environment:
MARIADB_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?set MYSQL_ROOT_PASSWORD in .env}"
MARIADB_DATABASE: "${WORDPRESS_DB_NAME:?set WORDPRESS_DB_NAME in .env}"
MARIADB_USER: "${WORDPRESS_DB_USER:?set WORDPRESS_DB_USER in .env}"
MARIADB_PASSWORD: "${MYSQL_PASSWORD:?set MYSQL_PASSWORD in .env}"
volumes:
- wordpress_db:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized"]
interval: 10s
timeout: 5s
retries: 20
start_period: 30s
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --max-allowed-packet=64M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
wordpress:
image: wordpress:6-php8.3-apache
container_name: wordpress
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:8080:80"
env_file:
- .env
environment:
WORDPRESS_DB_HOST: "${WORDPRESS_DB_HOST:?set WORDPRESS_DB_HOST in .env}"
WORDPRESS_DB_USER: "${WORDPRESS_DB_USER:?set WORDPRESS_DB_USER in .env}"
WORDPRESS_DB_PASSWORD: "${MYSQL_PASSWORD:?set MYSQL_PASSWORD in .env}"
WORDPRESS_DB_NAME: "${WORDPRESS_DB_NAME:?set WORDPRESS_DB_NAME in .env}"
WORDPRESS_DEBUG: "${WORDPRESS_DEBUG:-0}"
WORDPRESS_CONFIG_EXTRA: |
if (isset($$_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($$_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) {
$$_SERVER['HTTPS'] = 'on';
}
define('FORCE_SSL_ADMIN', true);
volumes:
- wordpress_data:/var/www/html
healthcheck:
test: ["CMD-SHELL", "php -r '$$c=@fsockopen(\"127.0.0.1\",80,$$e,$$s,5); exit($$c ? 0 : 1);'"]
interval: 30s
timeout: 10s
retries: 10
start_period: 60s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
wordpress_db:
name: wordpress_db
wordpress_data:
name: wordpress_data
WordPress se enlaza solo a 127.0.0.1:8080 y MariaDB no tiene configuración de ports, por lo que la base de datos no queda expuesta directamente a internet. Antes de una actualización formal, fija la serie de imágenes y comprueba la compatibilidad de WordPress, PHP y MariaDB; no uses latest a largo plazo.
Inicia y comprueba:
docker compose config >/dev/null
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 wordpress db
curl -I http://127.0.0.1:8080/wp-login.php
Cuando ambos servicios estén sanos, configura el proxy inverso. No ejecutes docker compose down -v durante la inicialización inicial; elimina el volumen de la base de datos.
Edita /etc/caddy/Caddyfile:
www.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:8080
request_body {
max_size 64MB
}
}
Comprueba y recarga Caddy:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -I https://www.example.com/wp-login.php
Caddy obtiene y renueva los certificados automáticamente y reenvía X-Forwarded-Proto. El WORDPRESS_CONFIG_EXTRA de Compose permite que WordPress reconozca correctamente el HTTPS externo. La "Dirección del sitio" y la "Dirección de WordPress" del panel de administración deben seguir usando https://www.example.com, de lo contrario aparecerán contenidos mixtos, bucles de inicio de sesión o redirecciones constantes en el panel. Para más detalles sobre el proxy inverso, consulta Guía completa del proxy inverso de Caddy en un VPS.
Abre https://www.example.com en el navegador y rellena el título del sitio, el nombre de usuario administrador, una contraseña segura y un correo dedicado. No pongas como nombre de usuario administrador admin ni uses la misma contraseña que la del VPS o el correo.
Gestiona estos ajustes inmediatamente después de la instalación:
- Confirma que la "Visibilidad en motores de búsqueda" coincide con tu plan de lanzamiento;
- Elimina las entradas, páginas, temas y plugins no usados por defecto;
- Configura los enlaces permanentes como "Nombre de la entrada" y luego prueba si los enlaces antiguos necesitan redirecciones;
- Configura la zona horaria, el idioma y los tamaños de miniatura de medios correctos;
- Añade una página de política de privacidad del sitio en los ajustes de privacidad;
- Activa la autenticación de dos factores para la cuenta de administrador (cuando el plugin o el servicio de alojamiento lo admitan).
No instales decenas de plugins de "optimización" desde el principio. Cada plugin adicional añade costes de ejecución de PHP, consultas a la base de datos, actualizaciones y auditoría de seguridad.
El wp_mail() predeterminado de WordPress depende de la capacidad de correo del servidor, y el puerto 25 está restringido en muchos VPS, lo que provoca la pérdida de restablecimientos de contraseña, notificaciones de formularios y correos de pedidos. Los sitios de producción deben usar un servicio SMTP fiable, configurar credenciales dedicadas mediante un plugin SMTP y probar:
- El restablecimiento de contraseña del administrador;
- Las invitaciones de nuevos usuarios o las notificaciones de registro;
- Los formularios de contacto y las notificaciones de pedidos;
- La coherencia de SPF, DKIM, DMARC y el dominio del remitente.
No escribas la contraseña maestra de tu correo personal en WordPress; usa preferiblemente contraseñas específicas de aplicación o una cuenta SMTP dedicada.
La base de datos guarda entradas, usuarios, ajustes y pedidos; el volumen wordpress_data guarda temas, plugins, imágenes subidas y wp-config.php. Si solo haces copia de la base de datos, tras la restauración se perderán los archivos multimedia; si solo haces copia de los archivos, las entradas y los datos de usuario quedarán incompletos.
Copia de seguridad de la base de datos:
cd /opt/wordpress
mkdir -p backups
docker compose exec -T db sh -c \
'mariadb-dump --single-transaction --quick --routines --triggers \
-u root -p"$MARIADB_ROOT_PASSWORD" "$MARIADB_DATABASE"' \
| gzip > "backups/wordpress-db-$(date +%F-%H%M).sql.gz"
Copia de seguridad del volumen de archivos de WordPress:
docker run --rm \
-v wordpress_data:/source:ro \
-v "$PWD/backups:/backup" \
alpine sh -c 'tar -C /source -czf /backup/wordpress-data.tar.gz .'
Copia al menos una copia de seguridad a otra máquina o a almacenamiento de objetos, restringe los permisos de acceso y verifica periódicamente. Puedes consultar Simulacro de copia de seguridad y restauración en un VPS para establecer un proceso cifrado, externo y de aceptación de la restauración.
Al restaurar, primero detén las escrituras de WordPress, restaura el volumen de archivos y la base de datos, y luego inicia la aplicación. La aceptación completa debe incluir el inicio de sesión del administrador, las entradas, las imágenes, los enlaces permanentes, la subida de medios y el correo, no solo si la página de inicio abre.
El núcleo de WordPress, los temas, los plugins y la base de datos pueden sufrir cambios estructurales. Haz una copia de seguridad antes de actualizar y registra las versiones actuales:
docker compose exec wordpress php -r \
'include "/var/www/html/wp-includes/version.php"; echo $wp_version, PHP_EOL;'
docker compose images
docker compose ps
Actualización de las imágenes de contenedor:
docker compose config >/dev/null
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=200 wordpress
Tras la actualización, verifica el inicio de sesión, la publicación de entradas, la subida de medios, los correos de formularios y la caché del front-end. Actualiza los plugins de pocos en pocos; si aparece una pantalla blanca o un 500, revierte primero el cambio más reciente en lugar de actualizar todos los plugins a la vez.
No uses docker compose down -v para resolver errores de WordPress ni elimines directamente los volúmenes wordpress_data o wordpress_db sin una copia de seguridad.
Como mínimo debes:
- Usar claves SSH y un usuario sudo normal, y permitir solo los puertos necesarios en el firewall del VPS;
- Usar contraseñas seguras y autenticación de dos factores para el administrador de WordPress;
- Eliminar plugins y temas desactivados y no instalar plugins pirateados de origen desconocido;
- Restringir
/xmlrpc.php, el endpoint de inicio de sesión y los intentos de fuerza bruta en el panel, usando un WAF o limitación de velocidad cuando sea necesario; - Actualizar periódicamente WordPress, PHP, temas y plugins, conservando una copia de seguridad recuperable antes de actualizar;
- No mapear el puerto de MariaDB a internet ni filtrar contraseñas de base de datos en entradas, registros o tickets;
- Configurar monitorización para los registros de Docker, el espacio en disco y los certificados HTTPS.
Si el sitio ya se ha vuelto lento, diagnostica PHP-FPM, OPcache, Redis, la caché y las consultas lentas según Qué hacer cuando WordPress en un VPS va lento. No trates los plugins de caché como una solución universal para la falta de recursos de base de datos o del host.
docker compose ps
curl -I http://127.0.0.1:8080/wp-login.php
docker compose logs --tail=200 wordpress
sudo journalctl -u caddy --no-pager -n 100
Confirma que el contenedor de WordPress sigue en ejecución, que el enlace de puerto es 127.0.0.1:8080 y comprueba si el contenedor fue OOM Killed por falta de memoria.
El nombre de host de la base de datos dentro de la red de Compose es db, no localhost. Comprueba si el usuario, la contraseña, el nombre de la base de datos y el volumen de datos existente en .env son coherentes:
docker compose logs --tail=200 db
docker compose exec wordpress getent hosts db
docker compose config
Cambiar .env no cambia automáticamente la contraseña de un usuario de MariaDB ya inicializado; en ese caso, usa comandos de gestión de la base de datos para cambiarla de forma sincronizada, o restaura desde una copia de seguridad, en lugar de eliminar el volumen y empezar de nuevo.
Confirma que Caddy termina el HTTPS, que las dos URL del sitio de WordPress son HTTPS y limpia la caché del plugin y las cookies del navegador. No configures al mismo tiempo modos SSL y reglas de redirección conflictivos con una CDN como Cloudflare.
Primero comprueba el request_body max_size de Caddy, luego el upload_max_filesize, el post_max_size y el límite de memoria de WordPress de PHP. Antes de aumentar los límites, confirma la capacidad de disco y de copias de seguridad para evitar que una sola subida agote el espacio del VPS.
- WordPress escucha solo en
127.0.0.1:8080y MariaDB no está expuesta a internet - El dominio, el HTTPS de Caddy y las URL del sitio de WordPress son coherentes
- Están configurados la contraseña segura del administrador, la autenticación de dos factores y el mínimo privilegio
- Las pruebas de restablecimiento de contraseña por SMTP, de formularios o de correos de pedidos son correctas
- Tanto la base de datos como
wordpress_datatienen copias de seguridad externas - Se ha completado un simulacro de restauración y se han verificado entradas, imágenes e inicio de sesión
- Las versiones están fijadas y con copia de seguridad antes de actualizar, y se comprueban 502, 500 y el espacio en disco después de actualizar
- Se han eliminado el contenido por defecto, los plugins desactivados y los temas innecesarios
Tras completar estos pasos, esta instalación de WordPress tiene una base de producción mantenible. Docker aísla la aplicación, Caddy se encarga del HTTPS, MariaDB de la persistencia de los datos, y las copias de seguridad, el correo y las actualizaciones de seguridad determinan si el sitio puede funcionar realmente a largo plazo.