Nextcloud con PHP 7 Nginx y SSL/TLS

15 minutos de lectura

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

1sudo ufw allow 'WWW Secure'

Nginx

Instalación de Nginx

1sudo apt-get -y install nginx

Configuración de Nginx

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

1worker_processes 4;
2
3server_tokens off;

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

1grep 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 :

1cd /var/www
2
3sudo wget https://download.nextcloud.com/server/releases/latest.tar.bz2

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

1sudo wget https://download.nextcloud.com/server/releases/latest.tar.bz2.sha256
2
3sha256sum -c latest.tar.bz2.sha256 < latest.tar.bz2

La salida del ultimo comando debería ser

1latest.tar.bz2: La suma coincide

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

1sudo wget https://download.nextcloud.com/server/releases/latest.tar.bz2.asc
2
3sudo wget https://nextcloud.com/nextcloud.asc
4
5gpg --import nextcloud.asc
6
7gpg --verify latest.tar.bz2.asc latest.tar.bz2

Esto nos devolverá una salida como esta:

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

Descomprimidos el archivo Nextcloud :

1sudo tar -xvf latest.tar.bz2

Borramos los ficheros y firmas descargados :

1sudo 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 :

1sudo adduser nextcloud
2
3Añadiendo el usuario `nextcloud' ...
4Añadiendo el nuevo grupo `nextcloud' (1003) ...
5Añadiendo el nuevo usuario `nextcloud' (1003) con grupo `next' ...
6Creando el directorio personal `/home/nextcloud' ...
7Copiando los ficheros desde `/etc/skel' ...
8Introduzca la nueva contraseña de UNIX:

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

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

Eliminamos todos los permisos para otros usuarios:

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

Instalación modulos PHP

1sudo apt install php7.0-mysql php7.0-common php7.0-gd php7.0-json php7.0-cli
1sudo apt install php7.0-curl php7.0-mbstring php7.0-xml php7.0-zip
1sudo 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

1sudo apt-get install php7.0-fpm

Configuración

Creamos el fichero

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

Y ponemos el siguiente código :

 1[nextcloud]
 2listen = /var/run/nextcloud.sock
 3
 4listen.owner = nextcloud
 5listen.group = www-data
 6
 7user = nextcloud
 8group = www-data
 9
10pm = ondemand
11pm.max_children = 3
12pm.process_idle_timeout = 60s
13;pm.max_requests = 500
14
15env[HOSTNAME] = $HOSTNAME
16env[PATH] = /usr/local/bin:/usr/bin:/bin
17env[TMP] = /tmp
18env[TMPDIR] = /tmp
19env[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:

1sudo systemctl edit php7.0-fpm.service

Añadimos el siguiente código:

1[Service]
2    UMask=0027

Reiniciamos el servicio para que actualice los nuevos valores :

1sudo systemctl restart php7.0-fpm.service

Dominios verificados

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

Cambiamos de usuario

1su nextcloud

Editamos el fichero de configuración

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

Y añadimos

1'trusted_domains' =>
2array (
3  0 => 'tu.dominio.es',
4),

Instalación y configuración MariaDB

Instalación de MariaDB

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

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

 1sudo mysql_secure_installation
 2
 3NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
 4      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!
 5
 6In order to log into MariaDB to secure it, we'll need the current
 7password for the root user.  If you've just installed MariaDB, and
 8you haven't set the root password yet, the password will be blank,
 9so you should just press enter here.
10
11Enter 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

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

Para el resto de preguntas seleccionamos Y

 1By default, a MariaDB installation has an anonymous user, allowing anyone
 2to log into MariaDB without having to have a user account created for
 3them.  This is intended only for testing, and to make the installation
 4go a bit smoother.  You should remove them before moving into a
 5production environment.
 6
 7Remove anonymous users? [Y/n] y
 8 ... Success!
 9
10Normally, root should only be allowed to connect from 'localhost'.  This
11ensures that someone cannot guess at the root password from the network.
12
13Disallow root login remotely? [Y/n] y
14 ... Success!
15
16By default, MariaDB comes with a database named 'test' that anyone can
17access.  This is also intended only for testing, and should be removed
18before moving into a production environment.
19
20Remove test database and access to it? [Y/n] y
21 - Dropping test database...
22 ... Success!
23 - Removing privileges on test database...
24 ... Success!
25
26Reloading the privilege tables will ensure that all changes made so far
27will take effect immediately.
28
29Reload privilege tables now? [Y/n] y
30 ... Success!
31
32Cleaning up...
33
34All done!  If you've completed all of the above steps, your MariaDB
35installation 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 :

1mysql -u root -p

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

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

Creamos una base de datos nueva llamada nextcloud

1CREATE DATABASE IF NOT EXISTS nextcloud;

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

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

Recargamos la tabla de privilegios

1flush privileges;

Salimos de la consola de MariaDB

1quit

Error de autenticación

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

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

La solución es :

1sudo mysql -u root
2use mysql;
3update user set plugin='' where User='root';
4flush privileges;
5exit;

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

1sudo 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:

 1upstream php-handler {
 2    server                        unix:/var/run/nextcloud.sock;
 3}
 4
 5server {
 6    listen                        80;
 7    listen                        [::]:80;
 8    server_name                   www.tu.dominio.com tu.dominio.com;
 9
10    # Path to the root of your installation
11    root                          /var/www/nextcloud/;
12
13    # Add headers to serve security related headers
14    add_header                    X-Content-Type-Options nosniff;
15    add_header                    X-XSS-Protection "1; mode=block";
16    add_header                    X-Robots-Tag none;
17    add_header                    X-Download-Options noopen;
18    add_header                    X-Permitted-Cross-Domain-Policies none;
19    add_header                    Strict-Transport-Security 'max-age=31536000; includeSubDomains;';
20
21    location = /robots.txt {
22      allow                     all;
23      log_not_found             off;
24      access_log                off;
25    }
26
27    location = /.well-known/carddav {
28      return                      301 $scheme://$host/remote.php/dav;
29    }
30
31    location = /.well-known/caldav {
32      return                      301 $scheme://$host/remote.php/dav;
33    }
34
35    # set max upload size
36    client_max_body_size          512M;
37    fastcgi_buffers               64 4K;
38
39    # Enable gzip but do not remove ETag headers
40    gzip                          on;
41    gzip_vary                     on;
42    gzip_comp_level               4;
43    gzip_min_length               256;
44    gzip_proxied                  expired no-cache no-store private no_last_modified no_etag auth;
45    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;
46
47    location / {
48      rewrite                   ^ /index.php$uri;
49    }
50
51    location ~ ^/.well-known/acme-challenge/* {
52      allow                     all;
53    }
54
55    location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
56      deny                      all;
57    }
58
59    location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) {
60      deny                      all;
61    }
62
63    location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) {
64      fastcgi_split_path_info   ^(.+\.php)(/.*)$;
65      include                   fastcgi_params;
66      fastcgi_param             SCRIPT_FILENAME $document_root$fastcgi_script_name;
67      fastcgi_param             PATH_INFO $fastcgi_path_info;
68      fastcgi_param             modHeadersAvailable true;
69      fastcgi_param             front_controller_active true;
70      fastcgi_pass              php-handler;
71      fastcgi_intercept_errors  on;
72      #fastcgi_request_buffering off;
73    }
74
75    location ~ ^/(?:updater|ocs-provider)(?:$|/) {
76      try_files                 $uri/ =404;
77      index                     index.php;
78    }
79
80    # Adding the cache control header for js and css files
81    # Make sure it is BELOW the PHP block
82    location ~* \.(?:css|js|woff|svg|gif)$ {
83      try_files                 $uri /index.php$uri$is_args$args;
84      add_header                Cache-Control "public, max-age=7200";
85      add_header                X-Content-Type-Options nosniff;
86      add_header                X-XSS-Protection "1; mode=block";
87      add_header                X-Robots-Tag none;
88      add_header                X-Download-Options noopen;
89      add_header                X-Permitted-Cross-Domain-Policies none;
90      # Optional: Don't log access to assets
91      access_log                off;
92    }
93
94    location ~* \.(?:png|html|ttf|ico|jpg|jpeg)$ {
95      try_files                 $uri /index.php$uri$is_args$args;
96      # Optional: Don't log access to other assets
97      access_log                off;
98    }
99}

Activamos el virtual host :

1sudo ln -s /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/nextcloud
1sudo systemctl restart nginx.service
2sudo 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

1sudo apt-get install certbot python-certbot-nginx

Obtención del certificado

 1sudo certbot --nginx
 2
 3Saving debug log to /var/log/letsencrypt/letsencrypt.log
 4
 5Which names would you like to activate HTTPS for?
 6 -------------------------------------------------------------------------------
 7 1: tu.dominio.es
 8 2: www.tu.dominio.es
 9 -------------------------------------------------------------------------------
10 Select the appropriate numbers separated by commas and/or spaces, or leave input
11 blank to select all options shown (Enter 'c' to cancel):

Pulsamos intro para seleccionar todos los dominios

 1Saving debug log to /var/log/letsencrypt/letsencrypt.log
 2Enter email address (used for urgent renewal and security notices) (Enter 'c' to
 3cancel):hefistion.arroba.mail.com
 4
 5-------------------------------------------------------------------------------
 6Please read the Terms of Service at
 7https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf. You must agree
 8in order to register with the ACME server at
 9https://acme-v01.api.letsencrypt.org/directory
10-------------------------------------------------------------------------------
11
12(A)gree/(C)ancel: a
13
14Obtaining a new certificate
15Saving debug log to /var/log/letsencrypt/letsencrypt.log
16Obtaining a new certificate
17Performing the following challenges:
18tls-sni-01 challenge for tu.dominio.es
19tls-sni-01 challenge for www.tu.dominio.es
20Generating key (1024 bits): /var/lib/letsencrypt/snakeoil/0006_key.pem
21Waiting for verification...
22Cleaning up challenges
23Generating key (2048 bits): /etc/letsencrypt/keys/0000_key-certbot.pem
24Creating CSR: /etc/letsencrypt/csr/0000_csr-certbot.pem
25Generating key (1024 bits): /var/lib/letsencrypt/snakeoil/0007_key.pem
26Deployed Certificate to VirtualHost /etc/nginx/sites-enabled/nextcloud for set(['www.tu.dominio.es', 'tu.dominio.es'])
27Deployed 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.

 1Please choose whether HTTPS access is required or optional.
 2-------------------------------------------------------------------------------
 31: Easy - Allow both HTTP and HTTPS access to these sites
 42: Secure - Make all requests redirect to secure HTTPS access
 5-------------------------------------------------------------------------------
 6Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
 7Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/default
 8The appropriate server block is already redirecting traffic. To enable redirect anyway, uncomment the redirect lines in /etc/nginx/sites-enabled/default.
 9-------------------------------------------------------------------------------
10Congratulations! You have successfully enabled https://tu.dominio.es and
11https://www.tu.dominio.es
12
13You should test your configuration at:
14https://www.ssllabs.com/ssltest/analyze.html?d=tu.dominio.es
15https://www.ssllabs.com/ssltest/analyze.html?d=www.tu.dominio.es
16-------------------------------------------------------------------------------
17
18IMPORTANT NOTES:
19 - Congratulations! Your certificate and chain have been saved at
20   /etc/letsencrypt/live/tu.dominio.es/fullchain.pem. Your
21   cert will expire on 2017-12-07. To obtain a new or tweaked version
22   of this certificate in the future, simply run certbot again with
23   the "certonly" option. To non-interactively renew *all* of your
24   certificates, run "certbot renew"
25 - If you like Certbot, please consider supporting our work by:
26
27   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
28   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.

 1sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
 2
 3Generating DH parameters, 2048 bit long safe prime, generator 2
 4This is going to take a long time
 5..................................................................................
 6.+...............................+....+.....................................................................................
 7.......................................................................................+..........................................................................................
 8.......................................................................................
 9.............................................................+........................
10.............+................+............................+...................
11.+..................................................+.....................+

Modificamos el fichero de configuración

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

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

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

Comprobamos la configuración:

1sudo nginx -t
2
3nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
4nginx: configuration file /etc/nginx/nginx.conf test is successful

Si no da error, reiniciamos el servidor

1sudo systemctl reload nginx

Renovar el certificados

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

1sudo certbot renew

Podemos simular la renovación con:

 1sudo certbot renew --dry-run
 2
 3Saving debug log to /var/log/letsencrypt/letsencrypt.log
 4
 5-------------------------------------------------------------------------------
 6Processing /etc/letsencrypt/renewal/tu.dominio.es.conf
 7-------------------------------------------------------------------------------
 8Cert not due for renewal, but simulating renewal for dry run
 9Renewing an existing certificate
10Performing the following challenges:
11tls-sni-01 challenge for tu.dominio.es
12tls-sni-01 challenge for www.tu.dominio.es
13Waiting for verification...
14Cleaning up challenges
15Generating key (2048 bits): /etc/letsencrypt/keys/0002_key-certbot.pem
16Creating CSR: /etc/letsencrypt/csr/0002_csr-certbot.pem
17** DRY RUN: simulating 'certbot renew' close to cert expiry
18**          (The test certificates below have not been saved.)
19
20Congratulations, all renewals succeeded. The following certs have been renewed:
21  /etc/letsencrypt/live/tu.dominio.es/fullchain.pem (success)
22** DRY RUN: simulating 'certbot renew' close to cert expiry
23**          (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

1crontab -e
2
3Select an editor.  To change later, run 'select-editor'.
4  1. /bin/ed
5  2. /bin/nano        <---- easiest
6  3. /usr/bin/vim.tiny
7
8Choose 1-3 [2]: 2

Añadimos

10 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

1[opcache]
2
3opcache.enable=1
4opcache.enable_cli=1
5opcache.interned_strings_buffer=8
6opcache.max_accelerated_files=10000
7opcache.memory_consumption=128
8opcache.save_comments=1
9opcache.revalidate_freq=1

Cache de datos con Apcu

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

1sudo apt install php-apcu -y

Iniciamos sesión con el usuario nextcloud

1su nextcloud

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

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

Salimos del usuario nextcloud :

1exit

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

1sudo systemctl restart php7.0-fpm.service