Nextcloud con PHP 7, Nginx y SSL/TLS

Después de un tiempo utilizando como servidor HTTP Lighttpd, y aprovechado una nueva instalación del sistema en la raspberry, he decidido probar la instalación de Nextcloud 12 sobre un servidor HTTP Nginx, primero por aprender a hacerlo y ya de paso comprobar su rendimiento.

Junto a Nextcloud y Nginx, instalare MariaDB como gestor de base de datos y mi certificado Letsencryt, que ya expliqué en este articulo como obtenerlo.

Para la nueva instalación voy a utilizar raspbian scretch como sistema operativo que ya viene con PHP 7 en sus repositorios.

Antes de empezar, si utilizas algun tipo de firewall en tu red, deberás dar permisos para el trafico HTTPS hacia tu servidor, yo utilizo UFW, como explique en este articulo

sudo ufw allow 'WWW Secure'

Nginx

Instalación de Nginx

sudo apt-get -y install nginx

Configuración de Nginx

Modificamos las directivas del fichero de configuración de Nginx /etc/nginx/nginx.conf :

worker_processes 4;

server_tokens off;     

worker_processes 4; : numero de procesadores disponibles, para un mayor rendimiento le asigno los 4. Para conocer el numero de procesadores disponibles :

grep processor /proc/cpuinfo | wc -l

server_tokens off; : se desactiva por razones de seguridad, evitamos que se envíe el numero de versión del servidor Nginx en el bloque HTTP.

Instalación de Nextcloud

Descargamos la ultima versión de Nextcloud :

cd /var/www

sudo wget https://download.nextcloud.com/server/releases/latest.tar.bz2

Descargamos su fichero SHA256 y verificamos la integridad del fichero descargado :

sudo wget https://download.nextcloud.com/server/releases/latest.tar.bz2.sha256

sha256sum -c latest.tar.bz2.sha256 < latest.tar.bz2

La salida del ultimo comando debería ser

latest.tar.bz2: La suma coincide

Verificamos la firma PGP y la fuente del archivo que acabamos de descargar :

sudo wget https://download.nextcloud.com/server/releases/latest.tar.bz2.asc

sudo wget https://nextcloud.com/nextcloud.asc

gpg --import nextcloud.asc

gpg --verify latest.tar.bz2.asc latest.tar.bz2

Esto nos devolverá una salida como esta:

gpg: Firmado el lun 22 may 2017 10:33:42 CEST usando clave RSA ID A724937A
gpg: Firma correcta de "Nextcloud Security <security@nextcloud.com>"
gpg: ATENCIÓN: ¡Esta clave no está certificada por una firma de confianza!
gpg:           No hay indicios de que la firma pertenezca al propietario.
Huellas digitales de la clave primaria: 2880 6A87 8AE4 23A2 8372  792E D758 99B9 A724 937A

Descomprimidos el archivo Nextcloud :

sudo tar -xvf latest.tar.bz2

Borramos los ficheros y firmas descargados :

sudo rm latest.tar.bz2* nextcloud.asc

Permisos Unix

Por temas de seguridad vamos a crear un usuario llamado nextcloud para gestionar Nextcloud.

Por defecto cuando implementamos un servidor HTTP, el usuario que lo administra suele ser www-data, nobody, apache, etc. Si vamos a tener varios sitios corriendo en el servidor, todos van a utilizar el mismo usuario para ser administrados.

Si uno de los sitios cae por un usuario malicioso, el atacante tendrá acceso a todos nuestros sitios.

Es por ello que se recomienda crear usuarios separados para cada sitio.

Vamos a cambiar el propietario al directorio /var/www/nextcloud y asignárselo al usuario nextcloud.

Nginx se puso en marcha con el usuario www-data, por lo que debe tener acceso de lectura al directorio /var/www/nextcloud para leer los recursos estáticos (HTML, CSS, JS, etc.), vamos a asignar el usuario nextcloud al grupo www-data y por último vamos a eliminar el acceso a la carpeta /var/www/nextcloud a otros usuarios.

Creamos el usuario nextcloud :

sudo adduser nextcloud

Añadiendo el usuario `nextcloud' ...
Añadiendo el nuevo grupo `nextcloud' (1003) ...
Añadiendo el nuevo usuario `nextcloud' (1003) con grupo `next' ...
Creando el directorio personal `/home/nextcloud' ...
Copiando los ficheros desde `/etc/skel' ...
Introduzca la nueva contraseña de UNIX:

Cambiamos el propietario y el grupo del directorio /var/www/nextcloud:

sudo chown -R nextcloud:www-data /var/www/nextcloud

Eliminamos todos los permisos para otros usuarios:

sudo chmod -R o-rwx /var/www/nextcloud

Instalación modulos PHP

sudo apt install php7.0-mysql php7.0-common php7.0-gd php7.0-json php7.0-cli  
sudo apt install php7.0-curl php7.0-mbstring php7.0-xml php7.0-zip
sudo apt install php-apcu

Instalando y configurando PHP-FPM

El módulo de PHP-FPM permite la comunicación entre el servidor Nginx y el protocolo FastCGI basado en PHP. Este módulo ejecuta los scripts PHP en un proceso independiente a Nginx con UID y GID diferentes, incluso diferentes php.ini, reduciendo el tiempo de respuesta.

Instalación PHP-FPM

sudo apt-get install php7.0-fpm

Configuración

Creamos el fichero

sudo nano  /etc/php/7.0/fpm/pool.d/nextcloud.conf

Y ponemos el siguiente código :

[nextcloud]
listen = /var/run/nextcloud.sock

listen.owner = nextcloud
listen.group = www-data

user = nextcloud
group = www-data

pm = ondemand
pm.max_children = 3
pm.process_idle_timeout = 60s
;pm.max_requests = 500

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

Permisos php-fpm

Debemos indicar a PHP-FPM con que permisos se crearan los archivo o carpetas dentro de Nextcloud, deben de coincidir con los mismos permisos que indicamos durante la instalación de Nextcloud:

sudo systemctl edit php7.0-fpm.service

Añadimos el siguiente código:

[Service]
    UMask=0027

Reiniciamos el servicio para que actualice los nuevos valores :

sudo systemctl restart php7.0-fpm.service

Dominios verificados

Debemos añadir nuestro dominio al fichero de configuración de nextcloud

Cambiamos de usuario

su nextcloud

Editamos el fichero de configuración

nano /var/www/nextcloud/config/config.php

Y añadimos

  'trusted_domains' =>
  array (
    0 => 'tu.dominio.es',
  ),

Instalación y configuración MariaDB

Instalación de MariaDB

sudo apt-get install -y mariadb-server mariadb-client

Terminada la instalación ejecutamos el siguiente script para proteger la base e datos:

sudo mysql_secure_installation

NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!

In order to log into MariaDB to secure it, we'll need the current
password for the root user.  If you've just installed MariaDB, and
you haven't set the root password yet, the password will be blank,
so you should just press enter here.

Enter current password for root (enter for none):

Como acabamos de realizar la instalación de MariaDB la contraseña del usuario root estará en blanco por lo que simplemente pulsaremos la tecla intro

Cambiamos la contraseña para el usuario root

Set root password? [Y/n] y
New password:
Re-enter new password:                                                                                                                                 
Password updated successfully!                                                                                                                         
Reloading privilege tables..                                                                                                                           
 ... Success!       

Para el resto de preguntas seleccionamos Y

By default, a MariaDB installation has an anonymous user, allowing anyone                                                                              
to log into MariaDB without having to have a user account created for                                                                                  
them.  This is intended only for testing, and to make the installation
go a bit smoother.  You should remove them before moving into a
production environment.

Remove anonymous users? [Y/n] y
 ... Success!

Normally, root should only be allowed to connect from 'localhost'.  This
ensures that someone cannot guess at the root password from the network.

Disallow root login remotely? [Y/n] y
 ... Success!

By default, MariaDB comes with a database named 'test' that anyone can
access.  This is also intended only for testing, and should be removed
before moving into a production environment.

Remove test database and access to it? [Y/n] y
 - Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!

Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.

Reload privilege tables now? [Y/n] y
 ... Success!

Cleaning up...

All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.

Accedemos con el usuario root a la consola de MariaDB, nos pedirá la contraseña que pusimos durante la instalación de MariaDB :

mysql -u root -p

Creamos un usuario nuevo en MariaDB, cambiando “miusuario” y “micontraseña” por los que correspondan en cada caso

CREATE USER 'miusuario'@'localhost' IDENTIFIED BY 'micontraseña';

Creamos una base de datos nueva llamada nextcloud

CREATE DATABASE IF NOT EXISTS nextcloud;

Damos todos los privilegios de la base de datos nextcloud al nuevo usuario que hemos creado anteriormente.

GRANT ALL PRIVILEGES ON nextcloud.* TO 'miusuario'@'localhost' IDENTIFIED BY 'micontraseña';

Recargamos la tabla de privilegios

flush privileges;

Salimos de la consola de MariaDB

quit

Error de autentificación

Si al ejecutar el comando mysql -u root -p aparece el siguiente error:

mysql -u root -p
Enter password:
ERROR 1698 (28000): Access denied for user 'root'@'localhost'

La solución es :

sudo mysql -u root
use mysql;
update user set plugin='' where User='root';
flush privileges;
exit;

Nombre de dominio y virtualhost

Si vamos a acceder a Nectcloud desde fuera de nuestra red local necesitamos enlazar nuestro nombre de dominio a la dirección IP de nuestro servidor.

Creeamos el archivo /etc/nginx/sites-available/nextcloud

sudo nano /etc/nginx/sites-available/nextcloud

Y pegamos el siguiente texto, cambiamos las líneas server_name tu.dominio.com y root /var/www/nextcloud/ según nuestra configuración:

upstream php-handler {
    server                        unix:/var/run/nextcloud.sock;
}

server {
    listen                        80;
    listen                        [::]:80;
    server_name                   www.tu.dominio.com tu.dominio.com;

    # Path to the root of your installation
    root                          /var/www/nextcloud/;

    # Add headers to serve security related headers
    add_header                    X-Content-Type-Options nosniff;
    add_header                    X-XSS-Protection "1; mode=block";
    add_header                    X-Robots-Tag none;
    add_header                    X-Download-Options noopen;
    add_header                    X-Permitted-Cross-Domain-Policies none;
    add_header                    Strict-Transport-Security 'max-age=31536000; includeSubDomains;';

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

    location = /.well-known/carddav {
      return                      301 $scheme://$host/remote.php/dav;
    }

    location = /.well-known/caldav {
      return                      301 $scheme://$host/remote.php/dav;
    }

    # set max upload size
    client_max_body_size          512M;
    fastcgi_buffers               64 4K;

    # Enable gzip but do not remove ETag headers
    gzip                          on;
    gzip_vary                     on;
    gzip_comp_level               4;
    gzip_min_length               256;
    gzip_proxied                  expired no-cache no-store private no_last_modified no_etag auth;
    gzip_types                    application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

    location / {
        rewrite                   ^ /index.php$uri;
    }

    location ~ ^/.well-known/acme-challenge/* {
        allow                     all;
    }

    location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
        deny                      all;
    }

    location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) {
        deny                      all;
    }

    location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) {
        fastcgi_split_path_info   ^(.+\.php)(/.*)$;
        include                   fastcgi_params;
        fastcgi_param             SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param             PATH_INFO $fastcgi_path_info;
        fastcgi_param             modHeadersAvailable true;
        fastcgi_param             front_controller_active true;
        fastcgi_pass              php-handler;
        fastcgi_intercept_errors  on;
        #fastcgi_request_buffering off;
    }

    location ~ ^/(?:updater|ocs-provider)(?:$|/) {
        try_files                 $uri/ =404;
        index                     index.php;
    }

    # Adding the cache control header for js and css files
    # Make sure it is BELOW the PHP block
    location ~* \.(?:css|js|woff|svg|gif)$ {
        try_files                 $uri /index.php$uri$is_args$args;
        add_header                Cache-Control "public, max-age=7200";
        add_header                X-Content-Type-Options nosniff;
        add_header                X-XSS-Protection "1; mode=block";
        add_header                X-Robots-Tag none;
        add_header                X-Download-Options noopen;
        add_header                X-Permitted-Cross-Domain-Policies none;
        # Optional: Don't log access to assets
        access_log                off;
    }

    location ~* \.(?:png|html|ttf|ico|jpg|jpeg)$ {
        try_files                 $uri /index.php$uri$is_args$args;
        # Optional: Don't log access to other assets
        access_log                off;
    }
}

Activamos el virtual host :

sudo ln -s /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/nextcloud
sudo systemctl restart nginx.service
sudo systemctl restart php7.0-fpm.service

SSL/TLS con Let’s Encrypt

Instalando Certbot

Para la obtención de nuestro certificado SSH proporcionado por Letsencryt, vamos a utilizar la herramienta Certbot, que nos facilitara la obtención del mismo.

Certbot ya viene habilitado en los repositorios de raspian scretch, además, instalamos el modulo para que configure automáticamente SSL en nginx

sudo apt-get install certbot python-certbot-nginx

Obtención del certificado

sudo certbot --nginx

Saving debug log to /var/log/letsencrypt/letsencrypt.log

Which names would you like to activate HTTPS for?
-------------------------------------------------------------------------------
1: tu.dominio.es
2: www.tu.dominio.es
-------------------------------------------------------------------------------
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel):

Pulsamos intro para seleccionar todos los dominios

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel):hefistion@gmail.com

-------------------------------------------------------------------------------
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf. You must agree
in order to register with the ACME server at
https://acme-v01.api.letsencrypt.org/directory
-------------------------------------------------------------------------------

(A)gree/(C)ancel: a

Obtaining a new certificate
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Obtaining a new certificate
Performing the following challenges:
tls-sni-01 challenge for tu.dominio.es
tls-sni-01 challenge for www.tu.dominio.es
Generating key (1024 bits): /var/lib/letsencrypt/snakeoil/0006_key.pem
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0000_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0000_csr-certbot.pem
Generating key (1024 bits): /var/lib/letsencrypt/snakeoil/0007_key.pem
Deployed Certificate to VirtualHost /etc/nginx/sites-enabled/nextcloud for set(['www.tu.dominio.es', 'tu.dominio.es'])
Deployed Certificate to VirtualHost /etc/nginx/sites-enabled/nextcloud for set(['www.tu.dominio.es', 'tu.dominio.es'])

Podemos elegir que todo el trafico HTTP hacia el servidor sea redirigido al puerto HHTPS, opción 2.

Please choose whether HTTPS access is required or optional.
-------------------------------------------------------------------------------
1: Easy - Allow both HTTP and HTTPS access to these sites
2: Secure - Make all requests redirect to secure HTTPS access
-------------------------------------------------------------------------------
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/default
The appropriate server block is already redirecting traffic. To enable redirect anyway, uncomment the redirect lines in /etc/nginx/sites-enabled/default.
-------------------------------------------------------------------------------
Congratulations! You have successfully enabled https://tu.dominio.es and
https://www.tu.dominio.es

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=tu.dominio.es
https://www.ssllabs.com/ssltest/analyze.html?d=www.tu.dominio.es
-------------------------------------------------------------------------------

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/tu.dominio.es/fullchain.pem. Your
   cert will expire on 2017-12-07. 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

Con esto ya tendremos nuestro certificado descargado e instalado y nginx configurado.

Diffie-Hellman

Creamos los parámetros Diffie-Hellman utilizados para establecer la conexión SSL/TLS.

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
..................................................................................
.+...............................+....+.....................................................................................
.......................................................................................+..........................................................................................
.......................................................................................
.............................................................+........................
.............+................+............................+...................
.+..................................................+.....................+

Modificamos el fichero de configuración

sudo nano /etc/nginx/sites-available/nextcloud

Y al final antes del ultimo }añadimos el siguiente código:

ssl_dhparam /etc/ssl/certs/dhparam.pem;

Comprobamos la configuración:

sudo nginx -t

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Si no da error, reiniciamos el servidor

sudo systemctl reload nginx

Renovar el certificados

El certificado ofrecido por letsencrypt solo es válido para tres meses, para renovarlo, simplemente ejecutamos:

sudo certbot renew

Podemos simular la renovación con:

sudo certbot renew --dry-run

Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/tu.dominio.es.conf
-------------------------------------------------------------------------------
Cert not due for renewal, but simulating renewal for dry run
Renewing an existing certificate
Performing the following challenges:
tls-sni-01 challenge for tu.dominio.es
tls-sni-01 challenge for www.tu.dominio.es
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0002_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0002_csr-certbot.pem
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/tu.dominio.es/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)

Si todo ha funcionado bien, podemos programar su renovación de forma automática

Si es la primera vez que vamos a programar una tarea en cron debemos elegir el editor de texto con el que trabajar

crontab -e

Select an editor.  To change later, run 'select-editor'.
  1. /bin/ed
  2. /bin/nano        <---- easiest
  3. /usr/bin/vim.tiny

Choose 1-3 [2]: 2

Añadimos

0 2 * * * /usr/bin/sudo 15 3 * * * /usr/bin/certbot renew --quiet

Con esto hacemos que cron se ejecute todos los días a las 2 de la mañana

Mejorar el rendimiento de Nextcloud

Cache de PHP: OPcache

Usaremos OPcache para optimizar el codigo PHP

Editamos el fichero /etc/php/7.0/fpm/php.ini y descomentamos o modificamos los siguientes valores

[opcache]

opcache.enable=1
opcache.enable_cli=1
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=1

Cache de datos con Apcu

Apcu cachea en memoria las versiones compiladas de las páginas PHP

sudo apt install php-apcu -y

Iniciamos sesión con el usuario nextcloud

su nextcloud

y añadimos la siguiente línea en el archivo /var/www/nextcloud/config/config.php:

'memcache.local' => '\OC\Memcache\APCu',

Salimos del usuario nextcloud :

exit

Para finalizar, reiniciamos PHP-FPM para que los nuevos camilos tengan efectos:

sudo systemctl restart php7.0-fpm.service

Referecias:

comentario powered by Disqus