Cómo tener múltiples sitios WordPress con Nginx, PHP y MySQL

¿Tienes muchas páginas en WordPress en muchos servidores y quieres unirlos en un solo servidor?, aquí aprenderás cómo tener muchas webs WordPress de forma segura, rápida y confiable en un solo servidor.

Muchos de nosotros somos de dejar las cosas “a modo básico” y cuando tenemos un servidor para varias webs decidimos nada más crear carpetas y aventar todo “así de jalón”, a veces hasta el user de root y luego nos hackean una y se vienen abajo todas y bueno, es un broncón…

En este caso, yo tengo algo así y se me ocurrió que para aquellos que les cuesta trabajo encontrar este tipo de guías, hacer una para que les pueda ser de utilidad.

Como pre-dato, esto considera que saben ya algunas cosas como manejo de Ubuntu, entienden lo básico y ya tienen instalado Nginx, PHP, MySQL y comandos de ese tipo…ya que si no, sería una guía enorme, además de que, entienden que esto es en un VPS/Dedicado y no un “servidor compartido” (web-hosting o como le conozcan) obvio…

Ahora sí, manos a la obra. Lo primero es configurar PHP, para esto, en su directorio de instalación, por ejemplo: /etc/php/7.4/fpm/pool.d/ encuentran esta ruta, puede variar pero la idea es que logren encontrar pool.d en esta verán un archivo llamado www.conf este vamos a ignorarlo ya que aquí es donde está “la piscina (por pool) de configuraciones”, aquí vamos a generar una configuración por cada sitio que tengamos en nuestro servidor como por ejemplo: misitio.conf mitienda.conf y así sucesivamente, yo lo que hago es usar el dominio (sin extensión) + .conf por ejemplo: google.com se convierte en google.conf y vamos a agregar una configuración como esta:

[misitio]
user = misitio
group = misitio
listen = /run/php/php7.4-fpm-misitio.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /

Aquí, la primer línea es el nombre de la .conf para que lleven un control más sencillo y deberán cambiarlo en user , group y donde está el listen después de php7.4-fpm-

Esta configuración, tiene 3 trucos, el primero es que el listen deberán corroborar el .sock de la configuración www.conf para asegurarse que esa es la ruta de la base del .sock original. Después, el listen.owner y el grupo deberán ser los mismos del PHP original y al final, pm y sus configuraciones las pueden cambiar para performance por sitio, es decir darles más prioridad al sitio 1 que al sitio 2 o cosas así…

Por último hay que deshabilitar el opcache para ello abrimos el 10-opcache y agregamos esta línea (o cambiamos):

opcache.enable=0

Ya que tenemos todos nuestros .conf para todos nuestros sitios listos, es hora de ir a Nginx.

En Nginx, lo que hay que hacer, es configurar cada virtual host por separado. Por ello, normalmente se utiliza el directorio sites-enabled y demás. Personalmente no me gusta, prefiero usar el conf.d pero esto queda a su gusto…

En este archivo de VHost les paso uno que les hará la vida más fácil y les explico un poco, es un poco largo así que prepárense…

fastcgi_cache_path /var/cache/nginx/misitio levels=1:2 keys_zone=MISITIO:500m inactive=5m;
limit_req_zone $binary_remote_addr zone=MISITIOLIMIT:10m rate=2r/s;
server
{
	listen 80;
	server_tokens off;
	server_name misitio.com;
	return 301 https://$server_name$request_uri;
}
server
{
	server_name misitio.com;
	ssl_certificate /etc/nginx/ssl/misitio.pem;
	ssl_certificate_key /etc/nginx/ssl/misitio.key;
	listen 443 ssl http2;
	server_tokens off;
	fastcgi_cache_key "$scheme$request_method$host$request_uri";
	fastcgi_hide_header X-Powered-By;
	fastcgi_cache_use_stale error timeout invalid_header http_500;
	proxy_hide_header X-Powered-By;

	add_header Referrer-Policy "no-referrer-when-downgrade";
	add_header X-Frame-Options SAMEORIGIN;
	add_header Strict-Transport-Security "max-age=31536000";
	add_header X-Content-Type-Options nosniff;
	add_header X-XSS-Protection "1; mode=block";
	set $no_cache 0;
	if ($request_method = POST)
	{
		set $no_cache 1;
	}
	if ($query_string != "")
	{
		set $no_cache 1;
	}
	if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)")
	{
		set $no_cache 1;
	}
	if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in")
	{
		set $no_cache 1;
	}

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

	client_max_body_size 100M;
	location /
	{
		if ($request_method = 'OPTIONS')
		{
			add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
			add_header 'Access-Control-Max-Age' 1728000;
			add_header 'Content-Type' 'text/plain; charset=utf-8';
			add_header 'Content-Length' 0;
			return 204;
		}
		try_files $uri $uri/ /index.php$is_args$args;
	}

	location ~ \.php$
	{
		fastcgi_read_timeout 60;
		proxy_read_timeout 60;
		fastcgi_buffers 128 4096k;
		fastcgi_buffer_size 4096k;
		fastcgi_index index.php;
		fastcgi_pass unix:/run/php/php7.4-fpm-misitio.sock;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		include fastcgi_params;
		fastcgi_send_timeout 60;
		fastcgi_cache_bypass $no_cache;
		fastcgi_no_cache $no_cache;
		fastcgi_cache_valid 200 301 302 5m;
		fastcgi_cache MISITIO;
		fastcgi_busy_buffers_size 4096k;
		fastcgi_temp_file_write_size 4096k;
	}
	location = /xmlrpc.php
	{
		deny all;
		access_log off;
		log_not_found off;
	}

	location ~ /\.ht
	{
		deny all;
	}

	location ~ /\.stub
	{
		deny all;
	}

	location ~* wp-config.php
	{
		deny all;
		access_log off;
		log_not_found off;
	}
	location ~ /\.
	{
		deny all;
		return 404;
	}
	location ~* (webpack.dev.js|webpack.prod.js|Browserslist|LICENCE|package.json|package-lock.json|deploy.sh)
	{
		deny all;
		return 404;
	}

	location ~ \.(asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|webp|json|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|webm|mpp|_otf|odb|odc|odf|odg|odp|ods|odt|ogg|pdf|png|pot|pps|ppt|pptx|ra|ram|svg|svgz|swf|tar|tif|tiff|_ttf|wav|wma|wri|xla|xls|xlsx|xlt|xlw|zip)$
	{
		expires 7d;
		etag on;
		add_header Pragma public;
		add_header Cache-Control "public";
		if_modified_since exact;
		log_not_found off;
		access_log off;
	}

	location = /favicon.ico
	{
		log_not_found off;
		access_log off;
	}

	location = /robots.txt
	{
		allow all;
		log_not_found off;
		access_log off;
	}

	location ~* /manifest.json
	{
		allow all;
		log_not_found off;
		access_log off;
	}
	location ~* /(?:uploads|files|wp-content|wp-includes|akismet)/.*.php$
	{
		deny all;
		access_log off;
		log_not_found off;
	}
	location ~ /\.(svn|git)/*
	{
		deny all;
		access_log off;
		log_not_found off;
	}
	location ~ /\.user.ini
	{
		deny all;
		access_log off;
		log_not_found off;
	}
	location ~ \wp-login.php$
	{
		limit_req zone=MISITIOLIMIT;
	}
}

Antes de explicar, quiero decir que quizá se me escapó alguna cosa ya que son las que uso en mis sitios, por lo que les tocará echarle un ojo por su cuenta más a profundidad para adaptarlos a los suyos.

Vamos por lo primero, generamos un caché, aquí presten atención al key_zone deberán cambiarlo por algún nombre del sitio, yo uso como dije, el dominio, por eso MISITIO , después por seguridad, vamos a limitar las peticiones al login 2 por segundo (se dejan 2 por el pre-flight). Después el server para redirigir del 80 al 443 (o a HTTPS para más sencillo).

Después configuraciones para el SSL, y usar HTTP2. server_tokens off; nos permite apagar que sepan cuál servidor usamos después algunas configuraciones de caché, en esta hide_header nos permite ocultar que usamos PHP (vamos, es WordPress sabemos que es PHP, pero …)

Después, evitamos que usen nuestra web en un iframe para evitar que carguen nuestra web en otros lados. Después, configuraciones para evitar el caché en algunas zonas, después algunas configuraciones bases de Nginx, y una configuración para “la subida de archivos” a través del body de la petición, quizá esta puedan bajarla de 100 megas pero yo subo un archivo en varios de mis sitios con unos excels un poco pesados… pero por si les sirve.

Después en nuestra location donde “entra toda petición” las peticiones OPTIONS (normalmente por pre-flight, googlea qué es esto si no sabes), les decimos “es correcta”…sin que procese nada.

Seguido, vamos a que cualquier archivo .php ejecute ciertas cosas, un timeout de 60 segundos, un buffer de 4 megas, y aquí viene otra cosa:

fastcgi_pass unix:/run/php/php7.4-fpm-misitio.sock;

Aquí es donde vamos a asignar el .sock que generamos en las configuraciones de PHP, así que pongan atención a que haga match con el suyo. Después, otras configuraciones de caché y archivos, y lo importante:

fastcgi_cache MISITIO;

Aquí, hay que usar el nombre de la key_zone de la primer línea.

Posteriormente configuraciones de seguridad y más caché para evitar que por ejemplo si tenemos un .git nos lean los cambios, accedan al xmlrpc y varias cosas así, aquí quiero anexar dos partes especiales, la primera:

location ~* /(?:uploads|files|wp-content|wp-includes|akismet)/.*.php$ {
  deny all;
  access_log off;
  log_not_found off;
 }

Esta evitará que puedan acceder desde navegador a archivos .php en uploads o archivos directos, al menos un poco más de seguridad…

Y por último:

location ~ \wp-login.php$ {
     limit_req zone=MISITIOLIMIT;
 }

En esta parte, hay que cambiar por el zone de la segunda línea, esto con la finalidad de como dije, limitar a 2 peticiones por segundo al login.

Y eso fue todo, básicamente hay que repetir entre cada sitio…Cabe destacar, que estas no son las mejores configuraciones, pero me han servido…

Ahora, vamos a MySQL, aquí hay que crear usuarios para cada sitio, un usuario, después de tenerlos, vamos a ejecutar la siguiente accediendo con root :

create database misitio; 
grant all on misitio.* to [email protected] identified by 'contrasena'; 
flush privileges;

Y pues cambiar el nombre de la base de datos, el nombre del usuario y la contraseña por las que ustedes prefieran. En caso de que ya existan las bases de datos, pues solo creen el usuario CREATE USER... y meter ese usuario a la base de datos ya creada.

Una vez hecho esto ya tenemos casi todo preparado, nos faltan 2 cosas, permisos y generar los usuarios de Linux básicamente.

Para los usuarios de Linux hay que agregarlos de la siguiente forma:

sudo groupadd misitio
sudo useradd -g misitio misitio

Reemplazando y ejecutando por cada sitio que tengamos. Tomando como base el de las configuraciones de PHP (por eso el usar el dominio como referencia).

Después hay que agregar esos grupos al grupo de www-data :

sudo usermod -a -G misitio www-data

Y ya tenemos casi todo listo, faltan los permisos:

sudo chown -Rf misitio:misitio /var/www/html/misitio/ 
sudo chmod -R 750 /var/www/html/misitio/ 
sudo find /var/www/html/misitio/ -type f -exec chmod 644 {} \;

Aquí hay que cambiar las rutas y los nombres de usuario/grupo.

¡Listo!, ya casi terminamos, digo casi porque solo falta el toque final, reiniciar los servicios:

sudo service php7.4-fpm restart
sudo nginx -s reload

Como dato adicional, con nginx -t pueden validar que las configs de nginx estén correctas y no se hayan comido algo o hayan escrito algo mal.

Como adicional, les recomiendo mucho el que le quiten el acceso por contraseña a SSH y solo por llave pública (public key) puedan entrar, hagan un respaldo diario de las bases de datos, almacenen sus configuraciones en Github, así como sus webs, también les recomiendo instalar algún sistema que bloquee ataques de fuerza al SSH (como Fail2Ban), revisar sus plugins, tener un cron que quite el wp-cron , tener un auto-actualizador nocturno con wp-cli en un cron que les actualice la web y sea más seguro, quitar plugins que no usen y puedan ser vector de ataque, validar cada plugin, cada que puedan echarle un ojo a los registros de Nginx por si alguien anda molestando. Usar CloudFlare como administrador de DNS para más seguridad y evitar exponer su IP y bueno…

Eso fue todo por ahora, si tienes mejoras que puedas adicionar por acá, déjala en los comentarios, sígueme aquí en Twitter (@asfo_dev) y deja tu “Clap” y mucha suerte, ojalá te haya servido esta guía.

¿Cuál es tu reacción?
+1
0
+1
0
+1
0
+1
0
+1
2
Total
0
Shares
Publicación anterior

Docker para principiantes — Parte 2

Siguiente Publicación

Cómo utilizar JWT en Angular de forma fácil

Publicaciones Relacionadas