.conf para nginx

Última revisión: 2 de octubre de 2021

Si utilizas nginx, a parte del fichero general de configuración del servidor, es probable que necesites un fichero de configuración para cada uno de los sitios web en los que trabajes, y aquí hay una configuración bastante más compleja.

En este ejemplo vamos a usa un dominio example.com, por lo que todo el tráfico de www.example.com será redirigido a example.com.

Configuración detallada por bloques

Configuración HTTP

La idea es que nada del sitio responda con HTTP, sino sólo con HTTPS, así que cualquier petición que llegue se hará una redirección automáticamente.

server {
  listen 80;
  listen [::]:80;
  server_name example.com www.example.com;
  return 301 https://example.com$request_uri;
  access_log off;
}

Configuración HTTPS para www

Como sólo vamos a hacer que responsa el «sin-www», le diremos a todo el tráfico que llegue por www que haga una redirección. En este caso hemos de activar toda la parte de certificados y SSL/TLS.

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 208.67.222.222 8.8.8.8 valid=300s;
  resolver_timeout 2s;
  add_header Referrer-Policy "strict-origin-when-cross-origin";
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;
  add_header X-XSS-Protection "1; mode=block";
  server_name www.example.com;
  return 301 https://example.com$request_uri;
  access_log off;
}

Configuración general

Un poco como el bloque anterior, la configuración general de un sitio seguro.

  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  # SSL
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  # SSL OCSP Stapling
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 208.67.222.222 8.8.8.8 valid=300s;
  resolver_timeout 2s;
  # Security headers
  add_header Referrer-Policy "strict-origin-when-cross-origin";
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;
  add_header X-XSS-Protection "1; mode=block";
  #logs
  access_log /var/log/nginx/example.com-access.log combined buffer=64k flush=5m;
  error_log /var/log/nginx/example.com-error.log;

Configuración de ruta y defecto

Le diremos a qué dominio ha de responder, y qué ficheros ha de leer por orden. Por norma general encontraremos en WordPress ficheros index.php, y si no existe, suele haber un index.html.

  #CONFIG
  server_name example.com;
  root /webs/example.com;
  index index.php index.html index.htm;

Configuración de caché (WP Super Cache)

Si tienes un plugin de caché, no podrá auto-configurarse como suele hacerlo con el .htaccess de Apache, así que deberemos añadir la configuración manualmente. En este ejemplo es la configuración para WP Super cache, pero debes revisar la del plugin que uses.

  set $cache_uri $request_uri;
  if ($request_method = POST) {
    set $cache_uri 'null cache';
  }
  if ($query_string != "") {
    set $cache_uri 'null cache';
  }
  if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-*.php)") {
    set $cache_uri 'null cache';
  }
  if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") {
    set $cache_uri 'null cache';
  }
  set $cachefile "/wp-content/cache/supercache/$http_host/$cache_uri/index.html";
  if ($https ~* "on") {
    set $cachefile "/wp-content/cache/supercache/$http_host/$cache_uri/index-https.html";
  }

Bloqueo / Permisos de los .ht<loquesea>

Por defecto bloquearemos los accesos externos a los ficheros de configuración, y este mismo. Aunque dejaremos accesos al «Well-Known».

  location ~ /\.well-known {
    allow all;
  }
  location ~ /\.ht {
    deny all;
  }

Ficheros estáticos

Daremos algo de vida a los ficheros estáticos, tanto para que no den errores, como para configurar su caché.

  location ~ /favicon.(ico|png) {
    log_not_found off;
    access_log off;
  }
  location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
  }
  location ~* \.(bmp|bz2|cur|doc|docx|exe|gif|eot|gz|htc|ico|jpeg|jpg|mid|midi|mp3|mp4|ogg|ogv|otf|png|ppt|pptx|rar|rtf|svg|svgz|tar|tgz|ttf|wav|webm|woff|woff2|xls|xlsx|zip)$ {
    expires max;
    add_header Cache-Control "public";
    log_not_found off;
    access_log off;
  }
  location ~* \.(atom|css|js|rss)$ {
    expires 7d;
    add_header Cache-Control "public";
    log_not_found off;
    access_log off;
  }
  location ~* \.(?:ttf|eot|woff|otf)$ {
    add_header Access-Control-Allow-Origin "*";
  }

Bloqueo de accesos a ficheros con datos

Con estos bloqueos impedimos el acceso a determinados ficheros del sistema, a configuraciones de WordPress o cambiamos la respuesta de lo que devuelve por defecto el servidor web.

  location ~* readme\.(html|txt) {
    deny all;
  }
  location ~* (licencia|license|LICENSE|olvasdel|lisenssi|liesmich)\.(html|txt) {
    deny all;
  }
  location ~* ^/wp-config {
    deny all;
  }
  location ~* ^/wp-cron\.php {
    deny all;
  }
  location ~* ^/wp-admin/(install|setup-config|upgrade)\.php {
    deny all;
  }
  location ~* ^/wp-admin/maint/repair\.php {
    deny all;
  }
  location ~* ^/wp-links-opml\.php {
    deny all;
  }
  location ~* ^/wp-content/mu-plugins/$ {
    return 404;
  }
  location ~* ^/wp-content/(plugins|themes)/(.+)/$ {
    return 404;
  }
  location ~* ^/wp-content/(?:uploads|files)/.+\.(html|js|php|shtml|swf)$ {
    deny all;
  }
  location ~* ^/wp-content/plugins/.+\.(aac|avi|bz2|cur|docx?|eot|exe|flv|gz|heic|htc|m4a|midi?|mov|mp3|mp4|mpe?g|ogg|ogv|otf|pdf|pptx?|rar|rtf|tar|tgz|tiff?|ttc|wav|wmv|xlsx?|zip) {
    deny all;
  }
  location ~* sftp-config.json {
    deny all;
  }
  location ~* (access|error)_log {
    deny all;
  }
  location ~* installer-log\.txt {
    deny all;
  }
  location ~* ^/wp-content/debug.log {
    deny all;
  }
  location ~* (^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$ {
    deny all;
  }

Mitigation CVE-2018-6389

Por norma general es mejor bloquear el posible ataque que se puede llevar a cabo de forma sencilla con la carga de todos los scripts. Para evitarlo es mejor bloquear su carga.

  location ~* load-(scripts|styles)\.php {
    deny all;
  }

Bloqueo del XML-RPC

Si no usamos esta tecnología (por ejemplo, desde las Apps de WordPress o tenemos desactivados los pingbacks, por seguridad es una buena opción.

  # BLOCK XML-RPC
  location ~* /xmlrpc\.php {
    deny all;
  }

Bloqueo de usuarios en la REST-API

Para evitar de una forma rápida y simple obtener el listado de usuarios de WordPress, podemos limitar el acceso a esta petición. No lo bloquees si estás usando esta funcionalidad de la REST-API.

  # BLOCK USERS API
  location ~* ^/wp-json/wp/v2/users/ {
    deny all;
  }

Configuración de las peticiones

Configuramos el servdor para que todas las peticiones que lleguen (y no se hayan bloqueado previamente) sean contestadas por el sistema. En este caso, se incluye el sistema de peticiones de la caché, que sería priopritaria a otras peticiones.

  location / {
    try_files $cachefile $uri $uri/ /index.php?$args;
  }

Ejecución de PHP

Configuraremos PHP para que pueda responder las peticiones de WordPress.

  # ROOT PHP
  location ~ .php$ {
    fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
    fastcgi_index index.php;
    fastcgi_buffers 256 16k;
    fastcgi_buffer_size 128k;
    fastcgi_busy_buffers_size 256k;
    fastcgi_temp_file_write_size 256k;
    fastcgi_hide_header X-Powered-By;
    fastcgi_hide_header X-Pingback;
    fastcgi_hide_header Link;
    fastcgi_intercept_errors off;
    fastcgi_split_path_info ^(.+.php)(/.+)$;
    try_files $fastcgi_script_name =404;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PHP_ADMIN_VALUE open_basedir=$document_root/:/usr/lib/php/:/tmp/;
    fastcgi_param PATH_INFO $path_info;
    set $path_info $fastcgi_path_info;
    include fastcgi.conf;
  }

Contenido completo del .conf

El fichero completo quedaría de la siguiente manera:

# All HTTP traffic will be sent to HTTPS
server {
  listen 80;
  listen [::]:80;
  server_name example.com www.example.com;
  return 301 https://example.com$request_uri;
  access_log off;
}
# REDIRECTION
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  # SSL
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  # SSL OCSP Stapling
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 208.67.222.222 8.8.8.8 valid=300s;
  resolver_timeout 2s;
  # Security headers
  add_header Referrer-Policy "strict-origin-when-cross-origin";
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;
  add_header X-XSS-Protection "1; mode=block";
  server_name www.example.com;
  return 301 https://example.com$request_uri;
  access_log off;
}
# REAL SITE
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  # SSL
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  # SSL OCSP Stapling
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 208.67.222.222 8.8.8.8 valid=300s;
  resolver_timeout 2s;
  # Security headers
  add_header Referrer-Policy "strict-origin-when-cross-origin";
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;
  add_header X-XSS-Protection "1; mode=block";
  #logs
  access_log /var/log/nginx/example.com-access.log combined buffer=64k flush=5m;
  error_log /var/log/nginx/example.com-error.log;
  #CONFIG
  server_name example.com;
  root /webs/example.com;
  index index.php index.html index.htm;
  set $cache_uri $request_uri;
  if ($request_method = POST) {
    set $cache_uri 'null cache';
  }
  if ($query_string != "") {
    set $cache_uri 'null cache';
  }
  if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-*.php)") {
    set $cache_uri 'null cache';
  }
  if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") {
    set $cache_uri 'null cache';
  }
  set $cachefile "/wp-content/cache/supercache/$http_host/$cache_uri/index.html";
  if ($https ~* "on") {
    set $cachefile "/wp-content/cache/supercache/$http_host/$cache_uri/index-https.html";
  }
  location / {
    try_files $cachefile $uri $uri/ /index.php?$args;
  }
  # HIDDEN FILES
  location ~ /.well-known {
    allow all;
  }
  location ~ /.ht {
    deny all;
  }
  location ~ /favicon.(ico|png) {
    log_not_found off;
    access_log off;
  }
  location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
  }
  location ~* \.(bmp|bz2|cur|doc|docx|exe|gif|eot|gz|htc|ico|jpeg|jpg|mid|midi|mp3|mp4|ogg|ogv|otf|png|ppt|pptx|rar|rtf|svg|svgz|tar|tgz|ttf|wav|webm|woff|woff2|xls|xlsx|zip)$ {
    expires max;
    add_header Cache-Control "public";
    log_not_found off;
    access_log off;
  }
  location ~* \.(atom|css|js|rss)$ {
    expires 7d;
    add_header Cache-Control "public";
    log_not_found off;
    access_log off;
  }
  location ~* \.(?:ttf|eot|woff|otf)$ {
    add_header Access-Control-Allow-Origin "*";
  }
  location ~* readme\.(html|txt) {
    deny all;
  }
  location ~* (licencia|license|LICENSE|olvasdel|lisenssi|liesmich)\.(html|txt) {
    deny all;
  }
  location ~* ^/wp-config {
    deny all;
  }
  location ~* ^/wp-cron\.php {
    deny all;
  }
  location ~* ^/wp-admin/(install|setup-config|upgrade)\.php {
    deny all;
  }
  location ~* ^/wp-admin/maint/repair\.php {
    deny all;
  }
  location ~* ^/wp-links-opml\.php {
    deny all;
  }
  location ~* ^/wp-content/mu-plugins/$ {
    return 404;
  }
  location ~* ^/wp-content/(plugins|themes)/(.+)/$ {
    return 404;
  }
  location ~* ^/wp-content/(?:uploads|files)/.+\.(html|js|php|shtml|swf)$ {
    deny all;
  }
  location ~* ^/wp-content/plugins/.+\.(aac|avi|bz2|cur|docx?|eot|exe|flv|gz|heic|htc|m4a|midi?|mov|mp3|mp4|mpe?g|ogg|ogv|otf|pdf|pptx?|rar|rtf|tar|tgz|tiff?|ttc|wav|wmv|xlsx?|zip) {
    deny all;
  }
  location ~* sftp-config.json {
    deny all;
  }
  location ~* (access|error)_log {
    deny all;
  }
  location ~* installer-log\.txt {
    deny all;
  }
  location ~* ^/wp-content/debug.log {
    deny all;
  }
  location ~* (^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$ {
    deny all;
  }
  location ~* load-(scripts|styles)\.php {
    deny all;
  }
  # BLOCK XML-RPC
  location ~* /xmlrpc\.php {
    deny all;
  }
  # BLOCK USERS API
  location ~* ^/wp-json/wp/v2/users/ {
    deny all;
  }
  # ROOT PHP
  location ~ .php$ {
    fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
    fastcgi_index index.php;
    fastcgi_buffers 256 16k;
    fastcgi_buffer_size 128k;
    fastcgi_busy_buffers_size 256k;
    fastcgi_temp_file_write_size 256k;
    fastcgi_hide_header X-Powered-By;
    fastcgi_hide_header X-Pingback;
    fastcgi_hide_header Link;
    fastcgi_intercept_errors off;
    fastcgi_split_path_info ^(.+.php)(/.+)$;
    try_files $fastcgi_script_name =404;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PHP_ADMIN_VALUE open_basedir=$document_root/:/usr/lib/php/:/tmp/;
    fastcgi_param PATH_INFO $path_info;
    set $path_info $fastcgi_path_info;
    include fastcgi.conf;
  }
}

Seguir con Seguridad para WordPress


Sobre este documento

Este documento está regulado por la licencia EUPL v1.2, publicado en WP SysAdmin y creado por Javier Casares. Por favor, si utilizas este contenido en tu sitio web, tu presentación o cualquier material que distribuyas, recuerda hacer una mención a este sitio o a su autor, y teniendo que poner el material que crees bajo licencia EUPL.

Servicios de Administración de Sistemas WordPress

¿Tienes un sitio web con WordPress de alto tráfico? ¿Eres una Agencia con servidores con cPanel, Plesk u otro panel en los que mantienes WordPress para tus clientes?

Si es así y te interesa un servicio profesional de mantenimiento de infraestructura WordPress y de mejora del rendimiento de tus sitios web o los de tus clientes, contacta conmigo.