6

cPanel? No gracias – LEMP sin panel de control

cPanel ? No Gracias

Tras 15 años de amor y odio, trabajando con cPanel, habiendo desechado Plesk, y soportado Virtualmin, Webmin, VestaCp, DirectAdmin, y alguno que otro que se me queda en la memoria (Ensim), la final he optado por trabajar más con los clientes profesionales, sin panel de control ningún tipo, usando LEMP (Linux, Nginx, MySQL, Postfix). Pero sobre todo cPanel? No, gracias. La mejor alternativa a cPanel es no usar ningún panel de hosting

Actualizado: 03/12/2021

Contenidos

Escenario

Durante muchos años, he visto como las reacciones de cPanel y su equipo ante los avances de la industria han sido muy lentos, y muy ineficaces.

En su contra

  • mala implementación de los cambios en seguridad SSL,
  • pésimo soporte antispam,
  • sistema de documentación penoso,
  • no incorporación de Nginx como servidor web,
  • sistema de backups deficitario
  • y un largo etc.

A su favor decir, que es la herramienta perfecta para no dedicar mucho tiempo, sobre todo si tenemos un buen conocimiento de ella y creamos nuestras propias herramientas auxiliares.

Pero, hasta ahí. La compra de cPanel por parte de un fondo de inversón (aka fondo buitre), Oakley Capital, cuya primera medida fue la de comprar a su directo competidor, Plesk,  ademas de adquirir el desastroso WHMCS,  y subir los precios basándose en el cambio de potencia de las máquinas desde los últimos 20 años, con una desmesura inconcebible para los tiempos que vivimos, que me ha llevado a comenzar a guiar a los usuarios más avanzados a otra dirección, más eficaz y económica. 

Como ni siquiera los paneles OpenSource como VestaCp o ISPConfig me convencen, pues al final es más de lo mismo, ya que suponen la esclavitud de conocer y soportar a sus desarrolladores y su elenco de seguidores, para ofrecer una estabilidad que no es tal, y un ahorro de tiempo que tampoco lo es, lleno de dificultades intrínsecas a las peculiaridades de los distintos equipos de desarrollo, sus manías y sus preferencias. Así que creo que es mejor, aprender la base, que aprender lo que se sustenta sobre la base y que encima te quieren cobrar a precio de oro.

LEMP, Linux, Nginx, MySQL y Postfix

Para el artículo voy a usar Ubuntu 18.04 LTS, por muchas razones, aunque en próximos artículos trataré de describir lo mismo, pero usando Alpine Linux, que ya uso en mis deploys para el testing en Docker Gitlab. Aún no lo describo, pues es mucho más técnico y requiere bastante más especialización de usar Ubuntu LTS.

Instalación Nginx

Esta no es una guía para copy & paste. Sin entender muchos de los mecanismos de los que aquí se hablan, y sin comprenderlos, es posible que en un futuro tras una actualización, tu sistema quede roto, y no seas capaz de levantar Nginx. Es altamente recomendable, leer la documentación original de que cada apartado y tener en cuenta que las actualizaciones no son del estilo, sistema de paquetes tipo yum, apt-get, o similares. Abdelkarim Mateos

Es altamente recomendable, usar una instalación limpia de Ubuntu 18.04 LTS, para evitar posibles diferencias con aquellos ficheros de configuración que ya estuvieran instalado y/o manipulados. 

El uso de versiones LTS es altamente recomendable en entornos de desarrollo. Ir a la última, es el mejor camino para perder el tiempo y procastinar con nuestros trabajoAbdelkarim Mateos

Nginx oficial

En lugar de instalar los paquetes de Ubuntu, para Nginx instalaremos el repositorio oficial de nginx, que esta más actualizado y nos permite usar su estructura para crear (compilar módulos) dinámicos.

$ sudo apt install curl gnupg2 ca-certificates lsb-release
$ echo "deb http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \
    | sudo tee /etc/apt/sources.list.d/nginx.list
$ echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
    | sudo tee /etc/apt/preferences.d/99nginx
$ curl -o /tmp/nginx_signing.key https://nginx.org/keys/nginx_signing.key
$ gpg --dry-run --quiet --import --import-options show-only /tmp/nginx_signing.key
$ sudo mv /tmp/nginx_signing.key /etc/apt/trusted.gpg.d/nginx_signing.asc
$ sudo apt update
$ sudo apt install nginx
Antiguamente usaba los repositorios PPA de Ondrej, porque tiene varios módulos compilados y es bastante bueno, pero su compilación no me permite crear tus propios módulos en modo shared, o la menos a la escritura de este artículo no supe como hacerloAbdelkarim Mateos

Nginx con PPA 

No instalar con PPA, el texto de abajo, es sólo para conocerlo, porque habrá lectores que no quieran instalar Mod Security o porque se sientan más cómodos usando este método, suficiente para sus necesidades.

lsb_release -cs debe ir entre « comillas invertidas. Un error en el plugin de code no las muestra
$ sudo apt install -y build-essential git tree ntp ntpdate curl gnupg2 ca-certificates lsb-release
$ echo "deb http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" | sudo tee /etc/apt/sources.list.d/nginx.list
$ curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add -
$ sudo apt-key fingerprint ABF5BD827BD9BF62
   pub   rsa2048 2011-08-19 [SC] [expires: 2024-06-14]
         573B FD6B 3D8F BC64 1079  A6AB ABF5 BD82 7BD9 BF62
   uid           [ unknown] nginx signing key <[email protected]>
$ sudo apt update && sudo apt install nginx 

Nginx + Mod Security 3

Es una versión actualizada y optimizada de la versión oficial Compiling and Installing ModSecurity for NGINX Open Source

En mi caso prefiero tener un usuario, el mio, en el que suele colocar un directorio llamado software, donde realizo las operaciones tanto de compilación como de actualización de ciertos paquetes, como la pesadilla de WHMCS.

$ sudo apt-get install -y apt-utils autoconf automake build-essential git libcurl4-openssl-dev libgeoip-dev liblmdb-dev libpcre++-dev libtool libxml2-dev libyajl-dev pkgconf wget zlib1g-dev
$ mkdir soft && cd $_
$ git clone --depth 1 -b v3/master --single-branch https://github.com/SpiderLabs/ModSecurity
$ cd ModSecurity
$ git submodule init
$ git submodule update
$ ./build.sh  # No hacer caso del mensaje fatal: No names found, cannot describe anything.
$ ./configure
$ make
$ sudo make install
$ cd ..
$ git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git
$ nginx -v
nginx version: nginx/1.19.9 
$ version=1.19.9
$ wget http://nginx.org/download/nginx-$version.tar.gz
$ tar xvfz  nginx-$version.tar.gz
$ cd nginx-$version/
$ ./configure --with-compat --add-dynamic-module=../ModSecurity-nginx
$ make modules
$ sudo cp objs/ngx_http_modsecurity_module.so /usr/lib/nginx/modules/
$ sudo echo 'load_module modules/ngx_http_modsecurity_module.so;' > /usr/share/nginx/modules-available/mod-security.conf
$ sudo  ln -s /usr/share/nginx/modules-available/mod-security.conf  /etc/nginx/modules-enabled/50-mod-security.conf
$ sudo nano /etc/nginx/nginx.conf 
# antes de events, http o similar para activar los modulos dinamicos
include /etc/nginx/modules-enabled/*.conf;
$ sudo mkdir /etc/nginx/modsec
$ sudo wget -P /etc/nginx/modsec/ https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
$ sudo mv /etc/nginx/modsec/modsecurity.conf-recommended /etc/nginx/modsec/modsecurity.conf
$ sudo cp ../ModSecurity/unicode.mapping /etc/nginx/modsec/
$ sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/nginx/modsec/modsecurity.conf
$ sudo tee -a /etc/nginx/modsec/main.conf > /dev/null <<EOT
# From https://github.com/SpiderLabs/ModSecurity/blob/master/
# modsecurity.conf-recommended
#
# Edit to set SecRuleEngine On
Include "/etc/nginx/modsec/modsecurity.conf"

# Basic test rule
SecRule ARGS:testparam "@contains test" "id:1234,deny,status:403"
EOT

# Comprobar
$ sudo nginx -t
Durante la configuración de ModSecurity sale una serie de lineas con el error fatal: No names found, cannot describe anything. Es normal. Léase la documentación de los enlaces relativos a la compilación de ModSecurity

Editamos o creamos el fichero /etc/nginx/conf.d/default.conf en el cual haremos la carga de Mod Security.

$ sudo tee -a /etc/nginx/conf.d/default.conf > /dev/null <<EOT 
server {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
}
EOT
El uso de ficheros para atomizar las configuraciones, la carga de módulos, es una muy buena práctica, ya que así es más fácil modificar la configuración en ciertos caso como una actualización que rompa el funcionamiento de nginx, y que localizamos en uno u otro módulo.

Nginx + Módulo rDNS

Para algunos sitios uso el módulo rDNS que me permite bloquear el acceso por nombre de host dinámicos o estáticos. Más información de como compilarlo en mi wiki, Limitación de acceso por host dinámico – Apache y Nginx

Instalar OWASP Rules

Una vez más recomendar la lectura de la documentación final por si hay cambios y adecuarla al paso del tiempo, como con esté artículo. Ver la opción de más abajo más actualizada y eficaz.

$ wget https://github.com/SpiderLabs/owasp-modsecurity-crs/archive/v3.0.2.tar.gz
$ tar -xzvf v3.0.2.tar.gz
$ sudo mv owasp-modsecurity-crs-3.0.2 /usr/local/
$ sudo ln -s /usr/local/owasp-modsecurity-crs-3.0.2 /usr/local/owasp-modsecurity
$ sudo cp /usr/local/owasp-modsecurity/crs-setup.conf.example /usr/local/owasp-modsecurity/crs-setup.conf
Alternativa actualizada basada en Git

Yo soy más amigo de revisar el Git por aquello de usar la versión más actualizada en producción (nunca uso las versiones de desarrollo)

$ sudo cd /usr/local
$ sudo git clone https://github.com/SpiderLabs/owasp-modsecurity-crs.git
$ cd owasp-modsecurity-crs
$ git branch
* v3.3/dev
$ git branch -r
  origin/HEAD -> origin/v3.3/dev
  origin/gh-pages
  origin/master
  origin/v2.2/master
  origin/v2.2/owasp-honeypots
  origin/v3.0/dev
  origin/v3.0/master
  origin/v3.1/dev
  origin/v3.2/dev
  origin/v3.2/master  # Esta es la apropiada
  origin/v3.3/devq
$ git checkout -b v3.2 origin/v3.2/master
Branch 'v3.2' set up to track remote branch 'v3.2/master' from 'origin'.
Switched to a new branch 'v3.2'
$ git status # Verificación
On branch v3.2
Your branch is up to date with 'origin/v3.2/master'.

nothing to commit, working tree clean
$ sudo cp crs-setup.conf.example crs-setup.conf
$ cp rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
$ cp rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
$ cd ..
$ sudo cp -ar owasp-modsecurity-crs/ /usr/local/

Inicializar el fichero de configuración de Mod Security

Editar /etc/nginx/modsec/main.conf

sudo tee -a /etc/nginx/modsec/main.conf > /dev/null <<EOT
# 
# From https://github.com/SpiderLabs/ModSecurity/blob/master/
# modsecurity.conf-recommended
#
# Edit to set SecRuleEngine On
Include "/etc/nginx/modsec/modsecurity.conf"

# Basic test rule
# SecRule ARGS:testparam "@contains test" "id:1234,deny,status:403"
# OWASP CRS v3 rules
Include /usr/local/owasp-modsecurity-crs/crs-setup.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-901-INITIALIZATION.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-905-COMMON-EXCEPTIONS.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-910-IP-REPUTATION.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-911-METHOD-ENFORCEMENT.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-912-DOS-PROTECTION.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-913-SCANNER-DETECTION.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-921-PROTOCOL-ATTACK.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-931-APPLICATION-ATTACK-RFI.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf
Include /usr/local/owasp-modsecurity-crs/rules/RESPONSE-950-DATA-LEAKAGES.conf
Include /usr/local/owasp-modsecurity-crs/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf
Include /usr/local/owasp-modsecurity-crs/rules/RESPONSE-952-DATA-LEAKAGES-JAVA.conf
Include /usr/local/owasp-modsecurity-crs/rules/RESPONSE-953-DATA-LEAKAGES-PHP.conf
Include /usr/local/owasp-modsecurity-crs/rules/RESPONSE-954-DATA-LEAKAGES-IIS.conf
Include /usr/local/owasp-modsecurity-crs/rules/RESPONSE-959-BLOCKING-EVALUATION.conf
Include /usr/local/owasp-modsecurity-crs/rules/RESPONSE-980-CORRELATION.conf
Include /usr/local/owasp-modsecurity-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf

# Mias
Include /usr/local/owasp-modsecurity-crs/rules/REQUEST-903.9002-WORDPRESS-EXCLUSION-RULES.conf

# Whitelisting
Include /etc/nginx/modsec/whitelist/*.conf
EOT

Comprobar y hacer un reload

$ sudo nginx -s reload
nginx: [emerg] "modsecurity_rules_file" directive Rule id: 200000 is duplicated
in /etc/nginx/conf.d/default.conf:3

Ahora es momento de hacer algún testing como recomienda el documento de referencia.

Las pruebas, son algo necesario también de la administración de sistemas. No sólo ocupan el ámbito de los programadores. Los administradores de sistemas, también estamos obligados a verificar nuestro trabajo

Mod Security y las listas blancas

No es el alcance de este artículo describir como adecuar nuestras listas blancas, globales o por virtualhost, pero al menos voy a describir el proceso para la lista blanca global, ya que muchas reglas de OWASP son tremendas, y prácticamente hay decenas de ellas que son incompatibles con las enrevesadas formas de programar de algunos módulos, temas de Worpdress y de otros programas, además de la propia dureza de las reglas.

ATENCION Añadir rules a la lista blanca sin un minimo análisis, es el camino más rápido para convertir tu flamante sistema de seguridad, en un sistema de alarma que no funciona y te da la sensación de estar seguro.
$ sudo mkdir -p /etc/nginx/modsec/whitelist
$ sudo nano /etc/nginx/modsec/whitelist/global.conf
  SecRuleRemoveById 920170
  SecRuleRemoveById 921130
  SecRuleRemoveById 941100
  SecRuleRemoveById 941140
  SecRuleRemoveById 941160
  SecRuleRemoveById 949110

Securización Nginx

Generamos un certificado DHParam (duro)

También conocido como Forward Secrecy & Diffie Hellman Ephemeral Parameters

$ sudo openssl dhparam -out /etc/nginx/dh4096.pem 4096

Ciphers seguros

Tenemos dos opciones una para el 99,99% que es muy dura ya que muchos SO y navegadores se quedaran fuera de la posibilidade navegar por nuestro servidor (se puede ver la lista haciendo un check SSL en SSL Labs Test) u otra que es suficiente para obtener un grade A+, y no tener excesivos problemas con clientes antiguos.

Ciphers 99,99% seguros
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
Ciphers grado A+
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !MEDIUM";

Edición completa /etc/nginx/nginx.conf para seguridad adicional

Añadimos estas y otras que creemos de correcta aplicación. Si quieres más lectura al respecto tienes un buen artículo en Strong SSL Security on nginx y la configuración usada en nuestro gitlab.

# Nginx hardening
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !MEDIUM";
#ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
ssl_dhparam dh4096.pem;
ssl_ecdh_curve secp384r1;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;

Entorno multiusuario o virtual hosts en nginx

Bien, esta es una decisión de como plantear las cosas de acuerdo a tus necesidades. Hay decenas si no centenas de posibles vías. Lo suyo es que uno adecue a sus necesidades el como hacer esto, y que sea suyo.

Base de datos MySQL o MariaDB

Bueno, aquí llegamos a uno de esos puntos en los que el ser humano se enzarza en mil cuestiones, discusiones, con evangelistas de uno y otro lado. Yo soy más práctico, y uso MySQL 8 allí donde quiero por sus características y lo mismo con MariaDB con las suyas. Cada herramienta tiene sus pros y sus contras. 

Lo que si hay que tener en cuenta es que los backups de ambos no son iguales en muchos casos, y ni siquiera entre sus propias versiones lo son, así que ese mundo idílico de los forks, de la confianza en los backups, y  en el de no probar nuestros sistemas de backups, mejor olvidarlo. Hay que saber que y que no puedo hacer con cada sistema y con cada versión.[/]

MariadB

Nunca instalo el paquete de mi distribución ya sea esta, Ubuntu, Centos, Debian… Prefiero acudir a su página de instalación e instalar su repositorio original.

$ sudo apt-get install software-properties-common
$ sudo apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc'
$ sudo add-apt-repository 'deb [arch=amd64,arm64,ppc64el] http://mirror.terrahost.no/mariadb/repo/10.4/ubuntu bionic main'
$ sudo apt update
$ sudo apt install mariadb-server

MySQL

Con MySQL lo mismo usando sus repositorios oficiales. Primero hay que ver allí la versión actual, para evitar el descargar y subir, simplemente nos quedaremos con el dato de la version, al momento de escribir el artículo mysql-apt-config_0.8.15-1_all.deb

$ cd soft/
$ sudo apt-get install software-properties-common 
$ wget https://dev.mysql.com/get/mysql-apt-config_0.8.15-1_all.deb 
$ sudo dpkg -i mysql-apt-config_0.8.15-1_all.deb
$ sudo apt-get update
$ sudo apt-get install mysql-server
Merece la pena dedicar un poco de atención a la lectura del nuevo modelo de autentificación en MySQL 8. Socket vs Passwords, y tambien en el caso de las passwords por la posibilidad de usar un nuevo sistema más complejo. En la red se encunetran barbaridades sobre este tema, ya que como muchos copy & paste, no saben como trabajar con esto, prefieren ir directamente a seguir el modelo legacy, es decir el antiguo, con compatibilidad MySQL 5.7 y pasar por alto la oprtunidad de avanzar en la seguridad y el aprendizaje.

PHP, PHP-FPM con multiples versiones y usuarios

Instalar PHP con repositorio ppa

Excepto si se trata de un sólo usuario, un solo sitio, deberíamos plantear el sistema para usar PHP-FPM , con la posibilidad de usar multiples versiones de PHP.

Vamos a instalar PHP con FPM en múltiples versiones usando los repositorios PPA de Ondrej.

$ sudo add-apt-repository ppa:ondrej/php
$ sudo apt update
# Version 7.2
$ sudo apt install php7.2 php7.2-gd php-mysql php7.2-curl php7.2-zip php7.2-ldap php7.2-mbstring php-imagick php7.2-intl php7.2-xml php7.2-fpm unzip wget curl -y
# Version 7.4 requiere instalarlo en otro orden o se instalara apache
$ sudo apt install php7.4-fpm -y
$ sudo apt install php7.4-gd php-mysql php7.4-curl php7.4-zip php7.4-ldap php7.4-mbstring php-imagick php7.4-intl php7.4-xml php7.4-bcmath php7.4-common php7.4-soap php7.4-xsl php7.4-zip unzip wget curl -y
En esta instalación he usado php7.2 pero puedes instalar esta u otras versiones como la 7.3 ó la 7.4 al mismo tiempo por si tienes distintas webs o desarrollos con distintas versiones

php.ini

Los ficheros ini de la instalación vienen sin configuración de la zona horaria, lo cual suele producir algunos quebraderos de cabeza si los obviamos. Así que editaremos el fichero php.ini de a cada versión instalada

$ echo 'date.timezone = Europe/Madrid' | sudo tee -a /etc/php/7.2/fpm/php.ini
$ echo 'date.timezone = Europe/Madrid' | sudo tee -a /etc/php/7.3/fpm/php.ini

Entorno multiusuario o virtual hosts en nginx

En mi caso opto por que cada usuario tenga dos directorios. Uno para ubicar sus sitio web, y otro para sus configuraciones.

En la configuración primaria de un sitio, no instalo más que la version http, ya que después se encargará Cerbot de hacer la redirección a HTTPS.

$ mkdir -p ~/web/mysite.tld              # Donde pondré el software mi sitio 
$ mkdir -p ~/conf/web # Donde pondré la configuración nginx mysite.tld.conf
$ sudo nano /etc/nginx/conf.d/sites.conf # Donde pondré los enlaces a las configuraciones

Esta configuración requiere que por ejemplo al final del fichero de configuración de nginx (/etc/nginx/nginx.conf) este la linea que llama a la lectura de los ficheros *.conf y en ella ubicar un fichero que contenga la llamadas a todos los ficheros de configuración de sitios virtuales.

Esta es una opinión personal, que puede ser tomada en consideración o hacerse de la manera habitual, que sería que la la con¡figuración de los sitios virtuales estuviera en sites-available, y en sites-enabled el enlace simbólico a su correspondiente fichero.

include /etc/nginx/conf.d/sites.conf; 

El fichero sites.conf añadiríamos la linea a cada uno de los sitios virtuales.

include /home/MYUSER/conf/web/mysitio.tld.conf;
include /home/MYUSER/conf/web/mysitio2.tld.conf;
include /home/MYUSER2/conf/web/mysitio3.tld.conf
...
Dejo a disposición en mi Gitlab los dos ficheros de base que uso para este artículo, misitio.conf y nginx.conf
server {
    listen      80;
    server_name DOMAIN.TLD;
    root        /home/USER/web/DOMAIN.TLD;
    index       index.php index.html index.htm;
    access_log  /var/log/nginx/domains/DOMAIN.TLD.log combined;
    access_log  /var/log/nginx/domains/DOMAIN.TLD.bytes bytes;
    error_log   /var/log/nginx/domains/DOMAIN.TLD.error.log error;

    expires $expires;
    
    include /etc/nginx/modsec/active.conf;

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

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

    location / {
        try_files $uri $uri/ /index.php;
        
        if (!-e $request_filename)
        {
            rewrite ^(.+)$ /index.php?q=$1 last;
        }

        location ~* ^.+\.(jpeg|jpg|png|gif|bmp|ico|svg|css|js)$ {
            expires     max;
        }

        location ~* \.php$ {
	    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
	    if (!-f $document_root$fastcgi_script_name) {return 404;}
	    fastcgi_pass  unix:/run/php/MYSITE-fpm.sock;
	    fastcgi_index index.php;
	    include         /etc/nginx/fastcgi_params;
	    fastcgi_param SCRIPT_FILENAME;        
            $document_root$fastcgi_script_name;
        }
    }

    location ~* "/\.(htaccess|htpasswd)$" {
        deny    all;
        return  404;
    }
}

La configuración de arriba muestra tres palabras en MAYÚSCULAS que deberemos cambiar por nuestros valores

  • DOMAIN.TLD correspondiente al nombre de nuestro dominio
  • USER que correspodne al nombre de usuario donde estarán ubicados los ficheros del virtualhost
  • MYSITE que es el nombre que usaremos para el socket de php-fpm que usará este sitio virtual
Para trabajar con cualquier demonio o programa que me permita el uso de sockets, siempre preferire el uso de sockets frente al uso de puertos. Es muy habitual en los tutoriales encontrar la configuracion de php-fpm, basada en el uso de puertos. Más información PostgreSQL UNIX domain sockets vs TCP sockets

PHP-FPM por sitio virtual

Las bondades de usar una configuración por sitio virtual, son más de las queuno puede imaginar, y no es el alcance del artículo explicarlas. En nuestro caso es el sistema que usaremos, es decir, un fichero de configuración por sitio virtual

Creamos el fichero de configuración dentro del directorio correspondiente a la version de php a utilizar:
/etc/php/NUMBER.VERSION/fpm/pool.d/DOMAINNAME.conf

[DOMAINNAME]
;prefix = /path/to/pools/$pool
user = USER
group = GROUP
listen = /run/php/DOMAINNAME_com_MAYORNUMBERPHPVERSION_MINORNUMBERPHPVERSION-fpm.sock
listen.allowed_clients = 127.0.0.1

listen.owner = USER
listen.group = www-data # User nginx
;listen.mode = 0660

; Recomended initial setup 
pm = ondemand
pm.max_children = 10
pm.max_requests = 500
pm.process_idle_timeout = 10s
pm.status_path = /status

php_admin_value[upload_tmp_dir] = /home/USER/tmp
php_admin_value[session.save_path] = /home/USER/tmp
php_admin_value[session.gc_maxlifetime] = 7200
php_admin_value[zlib.output_compression] = 0

env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /home/USER/tmp
env[TMPDIR] = /home/USER/tmp
env[TEMP] = /home/USER/tmp    

Lista de cambios a realizar

  • DOMAINNAME por el nombre del dominio. No hace falta poner el FDQN sino la parte nominal. En la primera etiqueta sirve para crear el proceso FPM con esa etiqueta, lo que nos permite conocer en el sistema el proceso de cada sitio
  • MAYORNUMBERPHPVERSION_MINORNUMBERPHPVERSION = 7_4 ó 8_0
  • USER y GROUP para el user y su grupo en el que estamos ubicando los ficheros. Es importante ya que de lo contrario tendremos problemas con los permisos de directorios y ficheros. Muchas veces leeremos en internet, que debemos poner permisos 777, etc… nada más lejos de la realidad y de la seguridad de tu sistema.
Los valores ofrecidos aquí, son para una sitio con carga media-alta, y 12GB de RAM. Este artículo no pretenden ser un artículo completo, ni mucho menos una especialización en tunning o ajuste de rendimientos, para lo cual hay mucha literatura y buena, que te llevará a aprender a tunear tu servidor nginx con php, de forma más adecuada a tus necesidades.

Comprobación y reinicio

Antes de proseguir deberiamos comprobar que nuestro sitio esta operativo y funciona
Podemos crear un fichero de informacion de php (luego deberiamos borrarlo por seguridad) en nuestro home

    phpinfo();  // Muestra toda la informacion, pero debería eliminarse por seguridad 

Recordar que las palabras en MAYUSCULAS deben cambairse por el valor apropiado (7.2 por ejemplo)

$ sudo systemctl restart phpNUMBER.VERSION-fpm.service
$ sudo systemctl status phpNUMBER.VERSION-fpm.service
● php5.6-fpm.service - The PHP 5.6 FastCGI Process Manager
   Loaded: loaded (/lib/systemd/system/php5.6-fpm.service; enabled; vendor preset: enabled)
   Active: active (running) since Tue 2020-03-31 16:19:25 UTC; 17h ago
     Docs: man:php-fpm5.6(8)
  Process: 9806 ExecStopPost=/usr/lib/php/php-fpm-socket-helper remove /run/php/php-fpm.sock /etc/php/5.6/fpm/pool.d/www.conf 56 (code=exited, status=0/SUCCESS)
  Process: 9820 ExecStartPost=/usr/lib/php/php-fpm-socket-helper install /run/php/php-fpm.sock /etc/php/5.6/fpm/pool.d/www.conf 56 (code=exited, status=0/SUCCESS)
 Main PID: 9807 (php-fpm5.6)
   Status: "Processes active: 0, idle: 0, Requests: 1, slow: 0, Traffic: 0req/sec"
    Tasks: 1 (limit: 4915)
   CGroup: /system.slice/php5.6-fpm.service
           └─9807 php-fpm: master process (/etc/php/5.6/fpm/php-fpm.conf)

Mar 31 16:19:25 hq systemd[1]: Starting The PHP 5.6 FastCGI Process Manager...
Mar 31 16:19:25 hq systemd[1]: Started The PHP 5.6 FastCGI Process Manager.
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl restart nginx

HTTPS con Cerbot

Nuestro consejo es que acudas directamente a la página oficial de Cerbot, para obtener tu proceso de instalación.

Si que te aconsejamos a que dejes el proceso de forma global a Cerbot, incluido la ghestion de crearte las redirecciones a https de forma automatica con la opcion 2 redirect HTTP traffic to HTTPS, removing HTTP access

$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository universe
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install certbot python-certbot-nginx
$ sudo certbot --nginx
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
 
Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: hostname.full_qulified_name.tld
2: mysite.tld
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 
Obtaining a new certificate
Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/hostname.full_qulified_name.tld.conf
Deploying Certificate to VirtualHost /home/MYUSER/conf/web/mysite.tld.conf
 
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/hostname.full_qulified_name.tld.conf
Redirecting all traffic on port 80 to ssl in /home/MYUSER/conf/web/mysite.tld.conf
 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://hostname.full_qulified_name.tld.conf
Congratulations! You have successfully enabled https://mysite.tld.conf

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=hostname.full_qulified_name.tld
https://www.ssllabs.com/ssltest/analyze.html?d=mysite.tld

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/hostname.full_qulified_name.tld/fullchain.pem
   /etc/letsencrypt/live/mysite.tld/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/hostname.full_qulified_name.tld/privkey.pem
   /etc/letsencrypt/live/mysite.tld/privkey.pem
   Your cert will expire on 2020-06-10. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot again
   with the "certonly" option. To non-interactively renew *all* of
   your certificates, run "certbot renew"
 - If you like Certbot, please consider supporting our work by:
 
   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le    

Correo con Postfix, Postfixadmin, Dovecot con SASL

Postfix servidor de correo

Postfix en un servidor de correo, que en combinación con Dovecot, es una de las mejores opciones para esta tarea, y que nos premitira una fácil gestión de los dominios y usuarios gracias a PostfixAdmin

Esta es una instalación para Postfix usando MySQL + Dovecot + SASL

$ sudo service sendmail stop; sudo update-rc.d -f sendmail remove
$ sudo apt update
$ sudo DEBIAN_PRIORITY=low apt install postfix
...
$ sudo postconf -e 'home_mailbox= Maildir/'
$ sudo postconf -e 'virtual_alias_maps= hash:/etc/postfix/virtual'
$ sudo apt-get -y install postfix postfix-mysql postfix-doc mariadb-client openssl getmail4 binutils dovecot-imapd dovecot-pop3d dovecot-mysql dovecot-sieve dovecot-lmtpd saslauthd

Editar la configuración de Postfix

Editamos /etc/postfix/master.cf

[...]
submission inet n       -       -       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
smtps     inet  n       -       -       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
[...]    

Si queremos una buena referencia la tenemos en el documento de Linuxize.com

Instalar y configurar PostfixAdmin

Se recomienda no usar caracteres extendidos en la contraseña ya que esto da algunos problemas, que a la edición de este artículo no habia solventado y que preferí obviar
$ sudo systemctl restart postfix
$ VERSION=3.3.1
$ #EDITADO: 2021/01/29 El enlace ya no funcionaba proque han cambiado de formato el URI $ wget -q https://downloads.sourceforge.net/project/postfixadmin/postfixadmin/postfixadmin-${VERSION}/postfixadmin-${VERSION}.tar.gz
$ wget -O PostfixAdmin-${VERSION}.tar.gz https://sourceforge.net/projects/postfixadmin/files/latest/download $ tar xzf postfixadmin-${VERSION}.tar.gz $ sudo mv postfixadmin-${VERSION}/ /var/www/postfixadmin
$ # Revisar el nombre al descomprimir $ rm -f postfixadmin-${VERSION}.tar.gz $ sudo mkdir -p /var/www/postfixadmin/templates_c $ sudo chown -R www-data: /var/www/postfixadmin $ sudo mysql (si tocamos mysql 8 y pusimos old system password en lugar del sistema de sockets -> mysql -u root -p mysql > CREATE DATABASE postfixadmin;
mysql > CREATE USER 'postfixadmin'@'localhost' IDENTIFIED BY 'PaswordSinCaracateresPeroLargo';
mysql > GRANT ALL ON postfixadmin.* TO 'postfixadmin'@'localhost';
mysql > FLUSH PRIVILEGES; $ sudo nano /var/www/postfixadmin/config.local.php

Inicialización Postfixadmin

$ sudo -u www-data php /var/www/postfixadmin/public/upgrade.php
Updating database:
- old version: 0; target version: 1840
(If the update doesn't work, run setup.php?debug=1 to see the detailed error messages and SQL queries.)
updating to version 1 (MySQL)...   done
updating to version 2 (MySQL)...   done
updating to version 3 (MySQL)...   done
updating to version 4 (MySQL)...   done
updating to version 5 (MySQL)...   done
updating to version 79 (MySQL)...   done
updating to version 81 (MySQL)...   done
updating to version 90 (MySQL and PgSQL)...   done
updating to version 169 (MySQL)...   done
updating to version 318 (MySQL)...   done
updating to version 344 (MySQL)...   done
updating to version 373 (MySQL)...   done
...
updating to version 1836 (MySQL)...   done
updating to version 1837 (all databases)...   done
updating to version 1839 (all databases)...   done
updating to version 1840 (MySQL and PgSQL)...   done

Nginx Virtualhost para PostfixAdmin

Para el tema de crear host virtuales para PostfixAdmin, Webmail, SOGo Groupware, uso el método habitual de nginx para sitios virtuales, ya que esto estan controlados por root. Es decir, usaremos /sites-available/ y /sites-enabled/

/etc/nginx/sites-available/HOSTANAME.DOMAINNAME.conf

Tenemos que recordar que hay que cambiar las variables por los valores deseados

server {                                                                                                                                                                                                           
    server_name HOSTNAME.TLD;
    root        /var/www/postfixadmin/public;                                                                                                                                                                      
    index       index.php index.html index.htm;                                                                                                                                                                        
    access_log  /var/log/nginx/domains/HOSTNAME.TLD.log combined;                                                                                                                                                
    access_log  /var/log/nginx/domains/HOSTNAME.TLD.bytes bytes;                                                                                                                                                 
    error_log   /var/log/nginx/domains/HOSTNAME.TLD.error.log error;                                                                                                                                             
                                                                                                                                                                                                                   
    modsecurity on;                                                                                                                                                                                                
    modsecurity_rules_file /etc/nginx/modsec/main.conf;                                                                                                                                                            
                                                                                                                                                                                                                   
    location = /favicon.ico {                                                                                                                                                                                      
        log_not_found off;                                                                                                                                                                                         
        access_log off;                                                                                                                                                                                            
    }                                                                                                                                                                                                              
                                                                                                                                                                                                                   
    location = /robots.txt {                                                                                                                                                                                       
        allow all;                                                                                                                                                                                                 
        log_not_found off;                                                                                                                                                                                         
        access_log off;                                                                                                                                                                                            
    }                                                                                                                                                                                                              
                                                                                                                                                                                                                   
    location / {
        try_files $uri $uri/ /index.php;

        if (!-e $request_filename)
        {
            rewrite ^(.+)$ /index.php?q=$1 last;
        }

        location ~* ^.+\.(jpeg|jpg|png|gif|bmp|ico|svg|css|js)$ {
            expires     max;
        }

        location /postfixadmin {
            index index.php;
            try_files $uri $uri/ /postfixadmin/index.php;
        }

        location ~* \.php$ {
            fastcgi_split_path_info ^(.+?\.php)(/.*)$;
            if (!-f $document_root$fastcgi_script_name) {return 404;}
            fastcgi_pass  unix:/run/php/php7.2-fpm.sock;
            fastcgi_index index.php;
            include         /etc/nginx/fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }
    }

    location ~* "/\.(htaccess|htpasswd)$" {
        deny    all;
        return  404;
    }
}
Finalizar la instalación del sitio virtual
$ sudo mkdir /var/log/nginx/domains/
$ sudo chown -R nginx:adm /var/log/nginx/domains/
$ sudo nginx -t
$ sudo systemctl restart nginx

Si ejecutamos nuevamente la instalación de los certificados con cerbot este instalará el certificado para este nuevo sitio, y si usamos la mismas opciones (2) la redirección a https automatica.

    ...
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/HOSTNAME.TLD/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/HOSTNAME.TLD/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = HOSTNAME.TLD) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    server_name HOSTNAME.TLD;
    listen 80;
    return 404; # managed by Certbot


}

Administrador Postfixadmin

Deberemos de ir a la url de nuestro administrador de postfixadmin https://FDQN/setup.php, donde crearemos una contraseña de aplicacion.
Al crearla nos indicará donde escribirla y en nuestro caso las escribermos en /var/www/postfixadmin/config.local.php

$CONF['setup_password'] = 'c3782aeLOQUEWSEAc9a4203f58dd:f6f8cc9acefb1LOMEJORQUEe92669394a3';  // Evidentemente se sustituye por el hash que nos ha facilitado el instalador

Una vez salvada la edición, si hacemos un reload, podremos añadir un administrador y su contraseña, que una vez creado nos permitira acceder en el index normal, como administrador

Preparación de Postfix para dominios virtuales

Necesitamos crear un usuario vmail para este propósito.

$ sudo useradd -r -u 150 -g mail -d /var/vmail -s /sbin/nologin -c "Virtual Mail User" vmail
$ sudo mkdir -p /var/vmail
$ sudo chmod -R 770 /var/vmail
$ sudo chown -R vmail:mail /var/vmail
$ sudo mkdir -p /etc/postfix/sql/

Edición de /etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf

Recordar que será de aplicación usar los valores que hallamos usado en el paso de crear el usuario de postfixadmin y su contraseña

user = postfixadmin
password = strong_password
hosts = localhost
dbname = postfixadmin
query  = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'

Edición de /etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf

user = postfixadmin
password = strong_password
hosts = localhost
dbname = postfixadmin
query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' and mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1'

Edición de /etc/postfix/sql/mysql_virtual_alias_domain_maps.cf

user = postfixadmin
password = strong_password
hosts = localhost
dbname = postfixadmin
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'

Edición de /etc/postfix/sql/mysql_virtual_alias_maps.cf

user = postfixadmin
password = strong_password
hosts = localhost
dbname = postfixadmin
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
#expansion_limit = 100

Edición de /etc/postfix/sql/mysql_virtual_domains_maps.cf

user = postfixadmin
password = strong_password
hosts = localhost
dbname = postfixadmin
query          = SELECT domain FROM domain WHERE domain='%s' AND active = '1'
#query          = SELECT domain FROM domain WHERE domain='%s'
#optional query to use when relaying for backup MX
#query           = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '0' AND active = '1'
#expansion_limit = 100

Edición de /etc/postfix/sql/mysql_virtual_mailbox_limit_maps.cf

user = postfixadmin
password = strong_password
hosts = localhost
dbname = postfixadmin
query = SELECT quota FROM mailbox WHERE username='%s' AND active = '1'

Edición de /etc/postfix/sql/mysql_virtual_mailbox_maps.cf

user = postfixadmin
password = strong_password
hosts = localhost
dbname = postfixadmin
query           = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
#expansion_limit = 100
Las lineas comentadas esta ahi para conocerlas ya que segun sea al caso, como por ejemplo tener configurado un servidor MX Backup será necesario descomentarlas y configurarlas

Actualizar el fichero main.cfg de Postfix desde el shell

$ sudo postconf -e "myhostname = $(hostname -f)"
$ sudo postconf -e "virtual_mailbox_domains = proxy:mysql:/etc/postfix/sql/mysql_virtual_domains_maps.cf"
$ sudo postconf -e "virtual_alias_maps = proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf, proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf, proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf"
$ sudo postconf -e "virtual_mailbox_maps = proxy:mysql:/etc/postfix/sql/mysql_virtual_mailbox_maps.cf, proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf"
$ sudo postconf -e "smtpd_tls_cert_file = /etc/letsencrypt/live/FDQN/fullchain.pem"
$ sudo postconf -e "smtpd_tls_key_file = /etc/letsencrypt/live/FDQN/privkey.pem"
$ sudo postconf -e "smtpd_use_tls = yes"
$ sudo postconf -e "smtpd_tls_auth_only = yes"
$ sudo postconf -e "smtpd_sasl_type = dovecot"
$ sudo postconf -e "smtpd_sasl_path = private/auth"
$ sudo postconf -e "smtpd_sasl_auth_enable = yes"
$ sudo postconf -e "smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination"
$ sudo postconf -e "mydestination = localhost"
$ sudo postconf -e "mynetworks = 127.0.0.0/8"
$ sudo postconf -e "inet_protocols = ipv4"
$ sudo postconf -e "inet_interfaces = all"
$ sudo postconf -e "virtual_transport = lmtp:unix:private/dovecot-lmtp"

Actualizar el fichero master.cfg de Postfix desde el shell

[...]
submission inet n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
smtps     inet  n       -       n       -       -       smtpd
  -o syslog_name=postfix/smtps
#  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
[...]

Activar el servicio al arranque y reiniciar

$ sudo systemctl enable postfix
$ sudo systemctl restart postfix

SASL

SASL o Simple Authentication and Security Layer es un marco de trabajo que provee de mecanismo de autentificación y servicios de seguridad a distintos protocolos de conexión via reemplazo, creando una interface que permite el uso de nuevos y antiguos mecanismos con una capa añadida de seguridad.

Editamos el fichero /etc/default/saslauthd

# Should saslauthd run automatically on startup? (default: no)
START=yes
$ sudo systemctl restart saslauthd

Dovecot

Editar fichdero de configuración de Dovecot

/etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:/var/vmail/%d/%n
mail_privileged_group = mail
mail_uid = vmail
mail_gid = mail
first_valid_uid = 150
last_valid_uid = 150
/etc/dovecot/conf.d/10-auth.conf
auth_mechanisms = plain login
#!include auth-system.conf.ext
!include auth-sql.conf.ext
/etc/dovecot/dovecot-sql.conf.ext
driver = mysql
connect = host=localhost dbname=postfixadmin user=postfixadmin password=strong_password
default_pass_scheme = MD5-CRYPT
password_query = SELECT username as user, password, '/var/vmail/%d/%n' as userdb_home, 'maildir:/var/vmail/%d/%n' as userdb_mail, 150 as userdb_uid, 8 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'
user_query = SELECT '/var/vmail/%d/%u' as home, 'maildir:/var/vmail/%d/%u' as mail, 150 AS uid, 8 AS gid, concat('dirsize:storage=',  quota) AS quota FROM mailbox WHERE username = '%u' AND active = '1'
/etc/dovecot/conf.d/10-ssl.conf
No debe haber espacio entre el «menor que» y /etc. Se trata de un error del plugin que uso para mostrar el código que me obliga a separarlo
ssl = yes
ssl_cert = < /etc/letsencrypt/live/FDQN/fullchain.pem
ssl_key = < /etc/letsencrypt/live/FDQN/privkey.pem
/etc/dovecot/conf.d/15-lda.conf
postmaster_address = postmaster@your_domain_name.com
/etc/dovecot/conf.d/10-master.conf
# find lmtp
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0600
    user = postfix
    group = postfix
  }
}
 
#find the service auth section and change it to:
 
service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
  }
  unix_listener auth-userdb {
    mode = 0600
    user = vmail
    #group = vmail
  }
  user = dovecot
}
 
# Change the service auth-worker section to the following:
service auth-worker {
  user = vmail
} 
Ajustes en sistema
$ sudo chown -R vmail:dovecot /etc/dovecot
$ sudo chmod -R o-rwx /etc/dovecot
$ sudo systemctl enable dovecot
$ sudo systemctl restart dovecot
$ sudo systemctl restart postfix
Logs por separado para dovecot

Me gustan los logs por separado para los servicios, y no soy amigo de dejarlo todo al fichero de logs del sistema, asi que editamos /etc/dovecot/conf.d/10-logging.conf

log_path = /var/log/dovecot.log
Rotacion de logs de Dovecot

Editamos el fichero /etc/logrotate.d/dovecot

# dovecot SIGUSR1: Re-opens the log files.
/var/log/dovecot*.log {
  missingok
  notifempty
  delaycompress
  sharedscripts
  postrotate
  /bin/kill -USR1 `cat /var/run/dovecot/master.pid 2>/dev/null` 2> /dev/null || true
  endscript
}
Posibles problemas en permisos

Si no hicimo sbien la instalación, o si hicimos algo extraños, al visualizar el log de Postfix tras un reinicio podemos encontrarnos con mensajes como los de abajo.

Mar  2 18:06:10 templateu18 postfix/postsuper[6436]: fatal: scan_dir_push: open directory defer: Permission denied
...
 
Mar  2 18:11:50 templateu18 postfix/postfix-script[7534]: warning: not owned by root: /var/spool/postfix/etc
Mar  2 18:43:29 templateu18 postfix/qmgr[10264]: warning: private/smtp socket: malformed response
Solución a estos problemas
$ sudo chown -R postfix /var/spool/postfix/
$  sudo postfix set-permissions
$ sudo chown -R root:root /var/spool/postfix/etc
$ sudo chown -R root:root /var/spool/postfix/lib
$ sudo chown -R root:root /var/spool/postfix/usr
$ sudo chown postfix:postfix /var/spool/postfix/dev
$ sudo chown root:root /var/spool/postfix/dev/*
$ sudo chmod 755 /var/spool/postfix/etc
$ sudo chmod 755 /var/spool/postfix/dev
$ sudo chmod 755 /var/spool/postfix/lib
$ sudo systemctl restart postfix

Testing dovecot

Benditas las pruebas (testing), aunque estas sean unitarias, pues de ellas son la tranquilidad del administrador. Dovecot te pone a disposición una baterias de pruebas que deberías hacer, antes de pasar a producción tu servidor.

RoundCube

Roundcube es un webmail de amplio uso, que a mi entender no debe faltar en una instalación de un servidor o servicio que tiene correo electrónico.

Instalacion

Descarga e instalacion

Consultamos en la página oficial cual es la versión estable que podemos usar

$ VERSION=1.4.3
$ wget https://github.com/roundcube/roundcubemail/releases/download/$VERSION/roundcubemail-$VERSION-complete.tar.gz
$ tar xvfz roundcubemail-$VERSION-complete.tar.gz 
$ sudo mv roundcubemail-$VERSION /var/www/html/webmail
$ sudo mysql
mysql >  create database roundcubedb;
mysql >  create user 'roundcube'@'localhost' IDENTIFIED BY 'MyPasWord_Not_use_extended';
mysql > GRANT ALL PRIVILEGES ON roundcubedb.* to 'roundcube'@'localhost';
mysql > FLUSH PRIVILEGES;
mysql > exit;
$ sudo mysql roundcubedb < /var/www/html/webmail/SQL/mysql.initial.sql
$ sudo chown -R www-data:www-data /var/www/html/webmail/
$ sudo find /var/www/html/webmail/ -type d -exec chmod 750 {} \;
$ sudo find /var/www/html/webmail/ -type f -exec chmod 640 {} \;

Virtualhost para RoundCube

Como hicimos con PostfixAdmin, necesitamos un servidor virtual para nuestro RoundCube. Bien, en eeste caso, no voy a extender el tutorial sobre como hacer la instalación para cada usuario, sino que haré una instalación global

Debemos crear el fichero /etc/nginx/sites-available/webmail.conf recordando que debemos de sustituir aquello que esta en mayúscula (excepto la variable de nginx SCRIPT_FILENAME)

server {                                                                                                                                                                                                           
    server_name webmail.HOSTNAME.TLD;                                                                                                                                                                            
    root        /var/www/html/webmail;                                                                                                                                                                             
    index       index.php index.html index.htm;                                                                                                                                                                    
    access_log  /var/log/nginx/domains/webmail.HOSTNAME.TLD.log combined;                                                                                                                                        
    access_log  /var/log/nginx/domains/webmail.HOSTNAME.TLD.bytes bytes;                                                                                                                                         
    error_log   /var/log/nginx/domains/webmail.HOSTNAME.TLD.error.log error;                                                                                                                                     
                                                                                                                                                                                                                   
    modsecurity on;                                                                                                                                                                                                
    modsecurity_rules_file /etc/nginx/modsec/main.conf;                                                                                                                                                            
                                                                                                                                                                                                                   
    client_max_body_size 100M;                                                                                                                                                                                     
                                                                                                                                                                                                                   
    location = /favicon.ico {                                                                                                                                                                                      
        log_not_found off;                                                                                                                                                                                         
        access_log off;                                                                                                                                                                                            
    }                                                                                                                                                                                                              
                                                                                                                                                                                                                   
    location = /robots.txt {                                                                                                                                                                                       
        allow all;
        log_not_found off;
        access_log off;
    }

    location / {
        try_files $uri $uri/ /index.php?q=$uri&$args;
        
        location ~* ^.+\.(jpeg|jpg|png|gif|bmp|ico|svg|css|js)$ {
            expires     max;
        }

        location ~ [^/]\.php(/|$) {
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            if (!-f $document_root$fastcgi_script_name) {
                return  404;
            }

            fastcgi_pass    unix:/var/run/php/php7.2-fpm.sock;
            fastcgi_index   index.php;
            include         /etc/nginx/fastcgi_params;
        }

        location ^~ /data {
           deny all;
        }
    }

    location ~* "/\.(htaccess|htpasswd)$" {
        deny    all;
        return  404;
    }
}

Como en ocasiones anteriores si ejecutamos cerbot y seleccionamos la opción de que sea Cerbot quien maneje la redirección de HTTPS, se encargará de editar y configurar https en nuestra configuración.

Instalador en navegador

Después de instalarlo como paquete, debemos configurarlo, para lo cual acudiremos a la url htps://url-virtual-host-roundcube/installer/ que nos entregará un wizard o asistente de configuración. Más información en el Wiki de RoundCube

Redis

Hay muchas opciones para la mejora de las aplicaciones PHP, basadas cachear ciertas partes de la aplicación. Memcached, Redis y otros. Pero la verdad, para mi gusto las aplicaciones que soportan Redis, tienen las de ganar. Redis es un motorde bases de datos en memoria basado en almacenamiento de tablas de hashes. Su eficacia en software como Worpdress o Magento, es mas que notable, y altamente recomendable.

Instalar Redis

$ sudo nano /etc/redis/redis.conf

supervised systemd

$ sudo systemctl restart redis.service
$ sudo systemctl status redis.service
● redis-server.service - Advanced key-value store
   Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2020-03-19 13:59:51 CET; 5s ago
     Docs: http://redis.io/documentation,
           man:redis-server(1)
  Process: 14037 ExecStop=/bin/kill -s TERM $MAINPID (code=exited, status=0/SUCCESS)
  Process: 14040 ExecStart=/usr/bin/redis-server /etc/redis/redis.conf (code=exited, status=0/SUCCESS)
 Main PID: 14059 (redis-server)
    Tasks: 4 (limit: 4915)
   CGroup: /system.slice/redis-server.service
           └─14059 /usr/bin/redis-server 127.0.0.1:6379

Mar 19 13:59:51 hq.castris.com systemd[1]: Starting Advanced key-value store...
Mar 19 13:59:51 hq.castris.com systemd[1]: Started Advanced key-value store.

Comprobar el estado de redis con una tarea cron

Algunas aplicaciones o sus plugins, cuando estan configurados para usar Redis, no estan programadas de forma que si hay un fallo de comunicación con Redis son incapaces de detectar el fallo, y continuar sin usar Redis, por lo que es necesario tener un monitorizador del demonio para que en caso de que falle, nos avise y trate de reiniciar el demonio
Asi que vamos a crear un script que haga esta función en /usr/bin/redischeck.sh (como root)

#!/bin/sh

active=$(redis-cli ping)  # redis sin contraseña
# SI usamos proteccion por contraseña 
#  active=$(redis-cli -a +S62tFFXqTXhAQ1Y2X1PxUQNJASHSHSHFu4aS5iBZiCQfCz4wp6hrpCc62vNLlKXE3LPsJxBIgM6 ping)
hostname=$(hostname -f)

if [ "$active" != "PONG" ]; then
        echo "Redis ${hostname} down" | mail -s "Redis ${hostname} down" [email protected]
        systemctl restart redis
fi

Después creamos la tarea crontab

$ sudo crontab -e

*/1 * * * *  /usr/local/bin/redischeck.sh  > /dev/null 2>&1

Comprobación local de la interface de respuesta

$ sudo netstat -lnp | grep redis
tcp        0      0 127.0.0.1:6379          0.0.0.0:*               LISTEN      14059/redis-server
tcp6       0      0 ::1:6379                :::*                    LISTEN      14059/redis-server

Instalación de php-redis

sudo apt-get install php7.2-redis php7.3-redis

Enlaces interesantes

Disclaimer

La intención de este artículo es informativa, y formativa. No esta exento de errores, ni de posibles formas de hacer las cosas que puedan y deban ser mejoradas. Es un documento realizado y enteregado así, sin ningina garantía y ni obligación.
Si ves algo que crees que es mejorable, alguna errata, puedes comentarlo o puedes contactar conmigo para indicarme que cosas están mal o que cosas mejorarias. Lo estudiaré y en su caso lo incorporare con las debidas menciones a tu trabajo.
Aun si, si usas el documento, visitas los enlaces e investigas, seguro que aprenderás mucho más que leyendo los tutoriales y docuemntación de los paneles típicos de Hosting.

Agradecimientos

Como siempre gracias Unsplash y a Filiberto Santillán por la imagen que sirve de portada, que edite gracias a Canva

Comparte este articulo en

Comments 6

  1. Increíble artículo, la verdad. Yo uso HestiaCP, es como la revolución de Vesta luego de que los desarrolladores lo dejaran abandonado. También es cierto que muchas tareas las hago manualmente por lo que bien explicás en tu post: ineficacia, lentitud, intrusión, recursos excesivos. Te mando un abrazo y admiro tu conocimiento.

  2. buen artículo pero la duda para los que venimos de vesta cp , es que sucede si necesitamos migrar a otro server, los backup de vesta son interesantes ya que de un click puedes migrar un usuario con sus dominios vinculados empaquetadas en un tar…..

    1. Post
      Author

      Bueno. La verdad es que tengo dos vps vestaCp, y no vuelvo a instalar nada con ese panel. Intrusivo, y malo. En cuanto a los backups. Mejor tener tu propio sistema de backup que confiar en terceros. Un buen día lo comprobarás.

  3. Pingback: cPanel? Historias de desamor – Diario de un preso

    1. Post
      Author

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *