Cet article déroule la mise en place complète d’une stack Nextcloud durcie sur Debian 13 : stockage chiffré LUKS, socle web, Nextcloud Hub (synchro, partage, ACL), co-édition OnlyOffice, cache et verrouillage Redis, et double authentification TOTP obligatoire. L’objectif : un cloud personnel ou d’équipe, auto-hébergé, performant et sécurisé.
1. Vue d’ensemble de la stack
[ Serveur Nextcloud - Debian 13 ] | |-- LUKS .............. chiffrement du disque de données (au repos) |-- nginx + PHP 8.4-FPM socle web |-- MariaDB ........... base de données |-- Nextcloud Hub ..... synchro, partage, ACL (Group folders) |-- Redis ............. cache distribué + verrouillage transactionnel (file locking) |-- OnlyOffice ....... co-édition de documents (conteneur Docker + JWT) |__ MFA TOTP .......... double authentification obligatoire (+ codes de secours)
On vise les versions Nextcloud 34 (Hub) et OnlyOffice Docs récentes, sur Debian 13 (Trixie) avec PHP 8.4 (supporté par Nextcloud 34). Dans tout l’article : domaine cloud.exemple.com pour Nextcloud, office.exemple.com pour OnlyOffice, disque de données /dev/sdb, données montées sur /srv/nextcloud/data.
2. Prérequis
- Une Debian 13 à jour, avec un accès root/sudo et un nom de domaine pointant vers le serveur.
- Un second disque (ou partition) dédié aux données, qui sera chiffré.
- Des certificats TLS (Let’s Encrypt) pour les deux sous-domaines : tout doit être en HTTPS, sous peine de casser l’éditeur OnlyOffice (contenu mixte).
- Pour OnlyOffice : prévoir au moins 2 vCPU et 4 Go de RAM supplémentaires.
sudo apt update && sudo apt full-upgrade -y sudo apt install -y curl gnupg unzip cryptsetup
3. Chiffrement du stockage avec LUKS
On chiffre le disque de données avant d’y installer quoi que ce soit. Tout ce qui sera écrit par Nextcloud (fichiers, aperçus, base si on la déplace) reposera ainsi sur un volume chiffré au repos.
# Initialiser LUKS2 sur le disque de données (DÉTRUIT son contenu) sudo cryptsetup luksFormat /dev/sdb # Ouvrir le volume -> apparaît en /dev/mapper/ncdata sudo cryptsetup open /dev/sdb ncdata # Créer le système de fichiers et le point de montage sudo mkfs.ext4 /dev/mapper/ncdata sudo mkdir -p /srv/nextcloud/data sudo mount /dev/mapper/ncdata /srv/nextcloud/data
Pour un déverrouillage automatique au démarrage (serveur sans clavier), on utilise un fichier-clé stocké sur le système racine. Compromis de sécurité à connaître : si la racine est compromise, le volume devient lisible. Pour un vrai durcissement, préférez une passphrase saisie au boot ou un déverrouillage réseau (clevis + tang).
# Générer un fichier-clé et l'ajouter comme clé LUKS sudo mkdir -p /etc/luks && sudo chmod 700 /etc/luks sudo dd if=/dev/urandom of=/etc/luks/ncdata.key bs=4096 count=1 sudo chmod 400 /etc/luks/ncdata.key sudo cryptsetup luksAddKey /dev/sdb /etc/luks/ncdata.key # Récupérer l'UUID du disque chiffré sudo blkid -s UUID -o value /dev/sdb
On déclare ensuite le volume dans /etc/crypttab (déverrouillage au boot) et le montage dans /etc/fstab :
# /etc/crypttab ncdata UUID=VOTRE-UUID-LUKS /etc/luks/ncdata.key luks,discard # /etc/fstab /dev/mapper/ncdata /srv/nextcloud/data ext4 defaults 0 2
Pensez à sauvegarder l’en-tête LUKS (sans elle, les données sont irrécupérables) :
sudo cryptsetup luksHeaderBackup /dev/sdb --header-backup-file /root/ncdata-luks-header.img
4. Socle web : nginx, PHP 8.4 et MariaDB
sudo apt install -y nginx mariadb-server redis-server \ php8.4-fpm php8.4-gd php8.4-mysql php8.4-curl php8.4-mbstring \ php8.4-intl php8.4-gmp php8.4-bcmath php8.4-xml php8.4-zip \ php8.4-imagick php8.4-redis php8.4-apcu
On sécurise MariaDB puis on crée la base et l’utilisateur Nextcloud :
sudo mysql_secure_installation sudo mariadb -e "CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" sudo mariadb -e "CREATE USER 'nc_admin'@'localhost' IDENTIFIED BY 'MotDePasseDB';" sudo mariadb -e "GRANT ALL PRIVILEGES ON nextcloud.* TO 'nc_admin'@'localhost'; FLUSH PRIVILEGES;"
Quelques réglages PHP utiles dans /etc/php/8.4/fpm/php.ini (et le pool) pour Nextcloud :
memory_limit = 512M upload_max_filesize = 10G post_max_size = 10G max_execution_time = 3600 output_buffering = Off ; Cache d'opcode (recommandé par Nextcloud) opcache.enable = 1 opcache.interned_strings_buffer = 16 opcache.max_accelerated_files = 10000 opcache.memory_consumption = 128 opcache.save_comments = 1 ; APCu disponible aussi en CLI (pour occ) apc.enable_cli = 1
sudo systemctl restart php8.4-fpm
5. Installation de Nextcloud Hub
cd /tmp wget https://download.nextcloud.com/server/releases/latest.zip sudo unzip -q latest.zip -d /var/www/ sudo chown -R www-data:www-data /var/www/nextcloud
On installe Nextcloud en ligne de commande avec occ (toujours exécuté en tant que www-data), en pointant le répertoire de données vers le volume LUKS :
cd /var/www/nextcloud sudo -u www-data php occ maintenance:install \ --database "mysql" --database-name "nextcloud" \ --database-user "nc_admin" --database-pass "MotDePasseDB" \ --admin-user "admin" --admin-pass "MotDePasseAdmin" \ --data-dir "/srv/nextcloud/data"
On déclare le domaine de confiance et les URLs publiques :
sudo -u www-data php occ config:system:set trusted_domains 1 --value=cloud.exemple.com sudo -u www-data php occ config:system:set overwrite.cli.url --value=https://cloud.exemple.com sudo -u www-data php occ config:system:set overwriteprotocol --value=https sudo -u www-data php occ config:system:set default_phone_region --value=FR
Voici un vhost nginx minimal pour Nextcloud (TLS géré par Certbot) :
server {
listen 443 ssl http2;
server_name cloud.exemple.com;
root /var/www/nextcloud;
index index.php;
ssl_certificate /etc/letsencrypt/live/cloud.exemple.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cloud.exemple.com/privkey.pem;
client_max_body_size 10G;
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
location = /.well-known/carddav { return 301 /remote.php/dav; }
location = /.well-known/caldav { return 301 /remote.php/dav; }
location / {
try_files $uri $uri/ /index.php$request_uri;
}
location ~ \.php(?:$|/) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param HTTPS on;
}
}
sudo ln -s /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx
Enfin, on remplace le cron AJAX par un vrai cron système (recommandé), exécuté toutes les 5 minutes par www-data :
sudo -u www-data php occ background:cron
# crontab de www-data (sudo crontab -u www-data -e) */5 * * * * php -f /var/www/nextcloud/cron.php
6. Redis : cache distribué et verrouillage transactionnel
Nextcloud distingue le cache local (rapide, par serveur : APCu) du cache distribué et du verrouillage transactionnel des fichiers (file locking), confiés à Redis. Le file locking via Redis évite les corruptions lors d’accès concurrents.
On fait écouter Redis sur une socket Unix (plus rapide et plus sûr qu’un port TCP local) dans /etc/redis/redis.conf :
port 0 unixsocket /run/redis/redis-server.sock unixsocketperm 770
# Autoriser www-data à utiliser la socket Redis sudo usermod -aG redis www-data sudo systemctl restart redis-server
On déclare APCu + Redis dans la configuration de Nextcloud :
sudo -u www-data php occ config:system:set memcache.local --value='\OC\Memcache\APCu' sudo -u www-data php occ config:system:set memcache.distributed --value='\OC\Memcache\Redis' sudo -u www-data php occ config:system:set memcache.locking --value='\OC\Memcache\Redis' sudo -u www-data php occ config:system:set redis host --value=/run/redis/redis-server.sock sudo -u www-data php occ config:system:set redis port --value=0 --type=integer
Le bloc résultant dans config/config.php ressemble à ceci :
'memcache.local' => '\OC\Memcache\APCu',
'memcache.distributed' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => [
'host' => '/run/redis/redis-server.sock',
'port' => 0,
'timeout' => 1.5,
],
# Vérifier que Redis répond redis-cli -s /run/redis/redis-server.sock ping
7. OnlyOffice : co-édition de documents
Le serveur de documents OnlyOffice se déploie proprement dans un conteneur Docker. Depuis Docs 7.2, le JWT est activé par défaut : on fixe nous-mêmes le secret pour le reporter côté Nextcloud.
# docker-compose.yml
services:
onlyoffice:
image: onlyoffice/documentserver:latest
container_name: onlyoffice
restart: unless-stopped
ports:
- "127.0.0.1:8081:80"
environment:
- JWT_ENABLED=true
- JWT_SECRET=UnSecretJWTLongEtAleatoire
volumes:
- ./data:/var/www/onlyoffice/Data
- ./logs:/var/log/onlyoffice
sudo docker compose up -d
On expose OnlyOffice derrière nginx en HTTPS sur office.exemple.com (les en-têtes WebSocket sont indispensables pour l’éditeur) :
server {
listen 443 ssl http2;
server_name office.exemple.com;
ssl_certificate /etc/letsencrypt/live/office.exemple.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/office.exemple.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Côté Nextcloud, on active le connecteur et on renseigne l’URL + le même secret JWT :
sudo -u www-data php occ app:enable onlyoffice sudo -u www-data php occ config:app:set onlyoffice DocumentServerUrl --value="https://office.exemple.com/" sudo -u www-data php occ config:app:set onlyoffice jwt_secret --value="UnSecretJWTLongEtAleatoire"
Le secret JWT doit être strictement identique des deux côtés, sinon l’éditeur refuse de s’ouvrir. Alternative plus légère si vous ne voulez pas gérer Docker : l’application Community Document Server (tout-en-un) depuis la boutique d’applications, suffisante pour de petits usages.
8. Partage et permissions avancées (ACL)
Nextcloud offre plusieurs canaux de partage : entre utilisateurs et groupes internes, par lien public (avec mot de passe et expiration), par e-mail, et en fédération avec d’autres instances Nextcloud. La stratégie recommandée : cadrer d’abord ces canaux au niveau global, puis structurer le partage d’équipe avec Group folders et des ACL fines, plutôt que de multiplier les partages individuels (vite ingérables).
8.1 Réglages globaux de partage
Ces paramètres se pilotent dans Administration, Partage ou en ligne de commande. On active le partage et les liens publics, puis on les durcit (mot de passe obligatoire, expiration par défaut) :
# Activer le partage et les liens publics sudo -u www-data php occ config:app:set core shareapi_enabled --value=yes sudo -u www-data php occ config:app:set core shareapi_allow_links --value=yes # Durcir les liens publics : mot de passe obligatoire + expiration par défaut sudo -u www-data php occ config:app:set core shareapi_enforce_links_password --value=yes sudo -u www-data php occ config:app:set core shareapi_default_expire_date --value=yes sudo -u www-data php occ config:app:set core shareapi_expire_after_n_days --value=30 # Expiration aussi pour les partages internes et fédérés sudo -u www-data php occ config:app:set core shareapi_default_internal_expire_date --value=yes sudo -u www-data php occ config:app:set core shareapi_internal_expire_after_n_days --value=90 # Cloisonner : un utilisateur ne peut partager qu'avec les membres de ses groupes sudo -u www-data php occ config:app:set core shareapi_only_share_with_group_members --value=yes
Les permissions par défaut des nouveaux partages sont un masque de bits : 1 = lecture, 2 = mise à jour, 4 = création, 8 = suppression, 16 = repartage. On combine par addition (31 = tout, 1 = lecture seule, 17 = lecture + repartage) :
sudo -u www-data php occ config:app:set core shareapi_default_permissions --value=31
Pour interdire purement et simplement le partage à certains groupes (ex: stagiaires), utilisez la liste Exclure des groupes du partage dans Administration, Partage (plus lisible qu’en ligne de commande).
8.2 Group folders : des dossiers d’équipe
L’application Group folders crée des dossiers partagés rattachés à des groupes (et non à un propriétaire), avec quota dédié : idéal pour des espaces d’équipe pérennes (Projets, Direction, Compta…).
sudo -u www-data php occ app:enable groupfolders # Créer un dossier d'équipe et le rattacher à un ou plusieurs groupes sudo -u www-data php occ groupfolders:create "Projets" sudo -u www-data php occ groupfolders:group 1 equipe-projets write sudo -u www-data php occ groupfolders:group 1 direction read # Fixer un quota dédié au dossier (ex: 50 Go) sudo -u www-data php occ groupfolders:quota 1 50GB # Lister les Group folders et leur configuration sudo -u www-data php occ groupfolders:list
8.3 ACL avancées (par sous-dossier)
Par défaut, tous les membres des groupes rattachés ont le même accès sur tout le dossier. Les permissions avancées (ACL) permettent de descendre au niveau du sous-dossier voire du fichier, et d’accorder ou retirer finement lecture, écriture, création, suppression et repartage, par groupe ou par utilisateur.
# Activer les ACL sur le Group folder (id 1) sudo -u www-data php occ groupfolders:permissions 1 --enable-acl # Désigner un gestionnaire d'ACL (il éditera les règles depuis l'interface) sudo -u www-data php occ groupfolders:permissions 1 --manage-add-group equipe-projets
Les règles fines se posent ensuite dans l’application Fichiers : sur le dossier, ouvrir le panneau de détails puis l’onglet des permissions avancées, et ajouter des règles par sous-dossier. Exemple : Projets/Contrats en lecture seule pour equipe-projets mais en écriture pour direction. Le modèle est restrictif et hérité : une permission retirée à un niveau se propage aux sous-dossiers tant qu’on ne la ré-accorde pas explicitement plus bas.
8.4 Règles d’accès par politique (optionnel)
Pour des règles transverses, indépendantes de l’arborescence, l’application File access control ajoute un moteur de règles (conditions ET/OU) : bloquer le téléchargement de certains types de fichiers, interdire l’upload hors d’une plage d’IP, restreindre selon le groupe ou l’agent, etc.
sudo -u www-data php occ app:enable files_accesscontrol
Les règles se définissent ensuite dans Administration, Flux de travail. À manier avec prudence : une règle trop large peut bloquer des accès légitimes.
9. MFA TOTP obligatoire
La double authentification (2FA) ajoute un second facteur au mot de passe. On s’appuie ici sur le TOTP (codes à usage unique basés sur le temps, générés par une appli type Aegis, FreeOTP, Google Authenticator), complété par des codes de secours. Points clés à ne pas rater : l’ordre d’activation (sous peine de se verrouiller dehors) et la compatibilité avec les clients de synchro.
9.1 Applications à installer
sudo -u www-data php occ app:enable twofactor_totp sudo -u www-data php occ app:enable twofactor_backupcodes
Recommandé en complément : WebAuthn / passkeys (clé matérielle FIDO2 ou biométrie, résistant au hameçonnage) via twofactor_webauthn, et la journalisation des événements de sécurité via admin_audit :
sudo -u www-data php occ app:enable twofactor_webauthn sudo -u www-data php occ app:enable admin_audit
9.2 Enrôler un compte (AVANT d’imposer la 2FA)
- Se connecter à l’interface web avec le compte concerné (commencer par l’
admin). - Aller dans Paramètres personnels, Sécurité.
- Activer TOTP : scanner le QR code avec l’application d’authentification, puis saisir un premier code pour valider.
- Générer les codes de secours et les conserver dans un endroit sûr (gestionnaire de mots de passe, coffre) : ce sont eux qui sauvent en cas de perte du téléphone.
9.3 Rendre la 2FA obligatoire
Une fois l’admin (et idéalement les premiers utilisateurs) enrôlé, on impose la 2FA. Impératif : ne jamais activer l’obligation avant d’avoir configuré le TOTP + les codes de secours d’au moins un compte admin.
# Imposer la 2FA à tous les comptes sudo -u www-data php occ twofactorauth:enforce --on # Variante : cibler des groupes précis et exclure les comptes de service sudo -u www-data php occ twofactorauth:enforce --on --group=staff --exclude=service-accounts # Lever l'obligation (retour arrière) sudo -u www-data php occ twofactorauth:enforce --off # Vérifier l'état 2FA d'un compte (fournisseurs activés) sudo -u www-data php occ twofactorauth:state admin
9.4 Clients de synchro et WebDAV : mots de passe d’application
Point crucial souvent oublié. La 2FA TOTP s’applique à la connexion web, mais les clients de bureau/mobile et les accès WebDAV utilisent l’authentification basique et ne savent pas saisir un code TOTP. La bonne pratique est le mot de passe d’application : un jeton dédié par appareil, généré dans Paramètres personnels, Sécurité, Appareils et sessions. Il contourne la 2FA, est révocable individuellement, et évite d’exposer le mot de passe principal. À communiquer aux utilisateurs au moment où l’on active l’obligation, pour ne pas casser leurs synchronisations.
9.5 Administration et dépannage
# Activer/désactiver un fournisseur 2FA pour un utilisateur donné sudo -u www-data php occ twofactorauth:enable utilisateur totp sudo -u www-data php occ twofactorauth:disable utilisateur totp # Débloquer un compte verrouillé (téléphone ET codes de secours perdus) sudo -u www-data php occ twofactorauth:disable admin totp # Filet de sécurité ultime : lever l'obligation globale puis réactiver après remise en état sudo -u www-data php occ config:system:set twofactor_enforced --value false
Bonnes pratiques de durcissement :
- Conserver l’accès
occ(donc SSH au serveur) comme unique véritable porte de secours : c’est ce qui évite un verrouillage total. - Activer la protection anti-force brute de Nextcloud (active par défaut) et, si possible, un fail2ban sur les échecs de connexion.
- Proposer WebAuthn/passkeys en second facteur prioritaire : plus résistant au hameçonnage que le TOTP.
- Auditer les événements 2FA via
admin_audit(fichier de log dédié).
10. Vérifications
# État général et avertissements de configuration sudo -u www-data php occ status sudo -u www-data php occ config:system:get memcache.locking # Redis redis-cli -s /run/redis/redis-server.sock ping # Réglages recommandés / sécurité : voir Administration > Vue d'ensemble
- Page Administration, Vue d’ensemble : zéro avertissement de sécurité/configuration.
- Créer un document et l’ouvrir : l’éditeur OnlyOffice doit s’afficher (sinon, vérifier le secret JWT et le HTTPS des deux côtés).
- Sur un Group folder avec ACL : vérifier qu’un membre d’un groupe en lecture seule ne peut pas modifier un sous-dossier protégé.
- Se déconnecter/reconnecter : la demande de code TOTP doit apparaître ; tester aussi un mot de passe d’application sur le client de synchro.
- Scan externe :
scan.nextcloud.comdoit donner un bon score.
11. Points de vigilance
- LUKS : sauvegardez l’en-tête (
luksHeaderBackup) et réfléchissez au mode de déverrouillage (fichier-clé pratique mais moins sûr ; passphrase au boot ou clevis/tang pour un vrai durcissement). - ACL : modèle restrictif et hérité ; testez toujours avec un compte non-admin après avoir posé des règles.
- MFA : codes de secours générés avant l’obligation, mots de passe d’application pour les clients de synchro, et
occcomme porte de secours. - OnlyOffice : secret JWT identique des deux côtés, tout en HTTPS (le moindre contenu mixte casse l’éditeur) ; prévoir CPU/RAM.
- Redis : APCu en local + Redis pour le distribué/verrouillage ; via socket Unix avec
www-datadans le grouperedis. - Sauvegardes : base de données + dossier
data+config.php+ en-tête LUKS ; tester la restauration. - Cron : utiliser le cron système (pas AJAX) pour la fiabilité des tâches de fond.
12. Récapitulatif des commandes
# Chiffrement cryptsetup luksFormat /dev/sdb && cryptsetup open /dev/sdb ncdata mkfs.ext4 /dev/mapper/ncdata && mount /dev/mapper/ncdata /srv/nextcloud/data # Installation Nextcloud sudo -u www-data php occ maintenance:install --database mysql --data-dir /srv/nextcloud/data ... sudo -u www-data php occ config:system:set trusted_domains 1 --value=cloud.exemple.com # Redis (cache + verrouillage) sudo -u www-data php occ config:system:set memcache.locking --value='\OC\Memcache\Redis' # Partage & ACL sudo -u www-data php occ app:enable groupfolders sudo -u www-data php occ groupfolders:create "Projets" sudo -u www-data php occ groupfolders:permissions 1 --enable-acl # OnlyOffice sudo -u www-data php occ app:enable onlyoffice sudo -u www-data php occ config:app:set onlyoffice DocumentServerUrl --value="https://office.exemple.com/" # MFA TOTP obligatoire sudo -u www-data php occ app:enable twofactor_totp twofactor_backupcodes sudo -u www-data php occ twofactorauth:enforce --on
Conclusion
On obtient un Nextcloud complet et durci : données chiffrées au repos avec LUKS, performances et cohérence assurées par Redis, co-édition bureautique via OnlyOffice, partage granulaire par ACL, et accès protégé par une double authentification TOTP obligatoire. Une base solide, entièrement auto-hébergée — et tout à fait scriptable via Ansible pour un déploiement reproductible.
