├── .gitignore ├── README.md ├── group_vars └── all │ └── main.yml ├── hosts_distant.yml ├── nextcloud_install.yml └── roles ├── lemp_stack ├── defaults │ └── main.yml ├── handlers │ └── main.yml ├── tasks │ ├── configure_mariadb.yml │ ├── configure_nginx.yml │ ├── install.yml │ ├── main.yml │ └── secure_mariadb_nginx.yml └── templates │ └── my.cnf.j2 ├── letsencrypt_challenge ├── handlers │ └── main.yml ├── tasks │ └── main.yml └── templates │ └── acme.conf.j2 └── nextcloud_installation ├── defaults └── main.yml ├── handlers └── main.yml ├── tasks └── main.yml └── templates └── nextcloud.conf.j2 /.gitignore: -------------------------------------------------------------------------------- 1 | *retry 2 | *_scaleway_token 3 | admin.pub 4 | hosts.yml 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ansible-nextcloud 2 | 3 | Ansible playbook to deploy a Nextcloud 24 instance on a clean 22.04 Ubuntu server (LEMP PHP 8.1 stack) 4 | 5 | ## Prerequisites 6 | 7 | A clean Ubuntu 22.04 instance with root/sudo access. 8 | 9 | ## Create a VM on Scaleway 10 | 11 | ### Prerequisites 12 | 13 | * Generate an SSH key pair (called admin.key and admin.pub) 14 | * Put them on the remote host 15 | * Install prerequisites system packages 16 | 17 | ```bash 18 | sudo apt-get install python3 python3-dev python3-pip build-essential libssl-dev libffi-dev jq 19 | ``` 20 | 21 | * Install Python packages on the local machine 22 | 23 | ```bash 24 | pip3 install jinja2 PyYAML paramiko cryptography packaging 25 | ``` 26 | 27 | * Install Ansible (ideally from sources, as it evolves really fast) 28 | 29 | If you don't want to install it from sources, you can use the PPA (see Ansible documentation) 30 | 31 | ```bash 32 | sudo apt update 33 | sudo apt install software-properties-common 34 | sudo apt-add-repository --yes --update ppa:ansible/ansible 35 | sudo apt install ansible 36 | ``` 37 | 38 | ## Install Nextcloud 39 | 40 | ### DNS resolution 41 | 42 | With the Ubuntu 22.04 available with a public IP, it's important to choose a DNS name for your future nextcloud service. 43 | 44 | Use a DNS name provider to add a IPv4/6 A/AAAA record to map this DNS name to your server IP. 45 | 46 | ### Installation 47 | 48 | Now that you have a working Ubuntu 22.04 available and resolvable, you can either run the installation playbook from your local machine to install on the distant server or directly from the future Ubuntu Nextcloud server. 49 | 50 | ```bash 51 | ansible-playbook -i your_inventory.yml nextcloud_install.yml 52 | ``` 53 | 54 | The playbook will prompt you for variables like password or DNS names. Store them for future use. 55 | 56 | ### Result 57 | 58 | ## TODO 59 | 60 | * Automate Lets Encrypt certificate DNS challenge 61 | 62 | ## Sources 63 | 64 | * [github.com/felixfontein/acme-certificate](https://github.com/felixfontein/acme-certificate) 65 | -------------------------------------------------------------------------------- /group_vars/all/main.yml: -------------------------------------------------------------------------------- 1 | apt_packages: 2 | - sudo 3 | - nginx 4 | - mariadb-server 5 | - mariadb-client 6 | - python3-pip 7 | - python3-mysqldb 8 | - unzip 9 | 10 | php_apt_packages: 11 | - php 12 | - php-fpm 13 | - php-mysql 14 | - php-cli 15 | - php-common 16 | - php-json 17 | - php-opcache 18 | - php-readline 19 | - php-mbstring 20 | - php-xml 21 | - php-gd 22 | - php-curl 23 | - php-imagick 24 | - php-zip 25 | - php-bz2 26 | - php-intl 27 | - php-bcmath 28 | - php-gmp 29 | - libmagickcore-6.q16-6-extra 30 | 31 | pip3_dependancies: 32 | - cryptography 33 | - python-apt -------------------------------------------------------------------------------- /hosts_distant.yml: -------------------------------------------------------------------------------- 1 | all: 2 | hosts: 3 | drive.zwindler.fr: 4 | ansible_ssh_user: ubuntu 5 | -------------------------------------------------------------------------------- /nextcloud_install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | become: true 4 | vars_prompt: 5 | - name: mysql_root_password 6 | prompt: "MariaDB root password" 7 | private: yes 8 | - name: nextcloud_mariadb_password 9 | prompt: "Nextcloud MariaDB password" 10 | private: yes 11 | - name: your_domain 12 | prompt: "Your domain" 13 | private: no 14 | - name: nextcloud_port 15 | prompt: "Nextcloud HTTPS port" 16 | default: 443 17 | private: no 18 | - name: nextcloud_instance_name 19 | prompt: "Nextcloud instance name (URL will be https://[thisvalue].[your_domain])" 20 | default: "nextcloud" 21 | private: no 22 | - name: admin_email 23 | prompt: "Administrator email (for LE certs)" 24 | private: no 25 | roles: 26 | - {role: lemp_stack} 27 | - {role: letsencrypt_challenge} 28 | - {role: nextcloud_installation} 29 | -------------------------------------------------------------------------------- /roles/lemp_stack/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | php_version: "php8.1" 3 | 4 | apt_packages: 5 | - sudo 6 | - nginx 7 | - mariadb-server 8 | - mariadb-client 9 | - python3-pip 10 | - python3-mysqldb 11 | - unzip 12 | 13 | php_apt_packages: 14 | - php 15 | - php-fpm 16 | - php-mysql 17 | - php-cli 18 | - php-common 19 | 20 | pip3_dependancies: 21 | - cryptography 22 | - python-apt -------------------------------------------------------------------------------- /roles/lemp_stack/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "restart nginx" 3 | ansible.builtin.systemd: 4 | state: restarted 5 | enabled: yes 6 | name: nginx 7 | 8 | - name: "restart php-fpm" 9 | ansible.builtin.systemd: 10 | state: restarted 11 | enabled: yes 12 | name: "{{ php_version }}-fpm" 13 | 14 | - name: "restart mariadb" 15 | ansible.builtin.systemd: 16 | state: restarted 17 | enabled: yes 18 | name: mariadb 19 | -------------------------------------------------------------------------------- /roles/lemp_stack/tasks/configure_mariadb.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Change InnoDB configuration" 3 | lineinfile: 4 | path: "/etc/mysql/mariadb.conf.d/50-server.cnf" 5 | line: "{{item}}" 6 | insertafter: "[mysqld]" 7 | loop: 8 | - "innodb_large_prefix=true" 9 | - "innodb_file_format=barracuda" 10 | - "innodb_file_per_table=1" 11 | notify: "restart mariadb" 12 | 13 | - name: "Flush handlers" 14 | meta: flush_handlers 15 | -------------------------------------------------------------------------------- /roles/lemp_stack/tasks/configure_nginx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Create dirs with owner to nginx" 3 | file: 4 | path: "{{item}}" 5 | state: directory 6 | recurse: yes 7 | owner: www-data 8 | group: www-data 9 | loop: 10 | - "/usr/share/nginx/html" 11 | - "/usr/share/nginx/nextcloud" 12 | - "/etc/pki/cert/private" 13 | - "/etc/pki/cert/csr" 14 | - "/etc/nginx/ssl" 15 | notify: "restart nginx" 16 | 17 | - name: "Enable PATH in php-fpm config" 18 | lineinfile: 19 | path: "/etc/php/8.1/fpm/pool.d/www.conf" 20 | line: "env[PATH] = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 21 | notify: "restart nginx" 22 | 23 | - name: "Change php.ini memory setting to minimum requirements" 24 | lineinfile: 25 | path: "/etc/php/8.1/fpm/php.ini" 26 | line: "memory_limit = 512M" 27 | regexp: "^memory_limit =" 28 | backrefs: yes 29 | notify: "restart nginx" 30 | 31 | - name: "Flush handlers" 32 | meta: flush_handlers 33 | -------------------------------------------------------------------------------- /roles/lemp_stack/tasks/install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install Aptitude (recommended when using apt ansible module) and update repositories cache" 3 | apt: 4 | name: aptitude 5 | state: present 6 | update_cache: yes 7 | cache_valid_time: 3600 8 | 9 | - name: "Apt upgrade" 10 | apt: 11 | upgrade: yes 12 | 13 | - name: "Install prerequisites" 14 | apt: 15 | name: "{{item}}" 16 | state: present 17 | loop: 18 | - "{{ apt_packages }}" 19 | 20 | - name: "Add python 3 deps" 21 | pip: 22 | name: "{{item}}" 23 | loop: 24 | - "{{ pip3_dependancies }}" 25 | 26 | - name: "Install PHP packages" 27 | apt: 28 | name: "{{item}}" 29 | state: present 30 | loop: 31 | - "{{ php_apt_packages }}" 32 | -------------------------------------------------------------------------------- /roles/lemp_stack/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install packages and prerequisites" 3 | ansible.builtin.include_tasks: 4 | file: install.yml 5 | 6 | - name: "Configure nginx" 7 | ansible.builtin.include_tasks: 8 | file: configure_nginx.yml 9 | 10 | - name: "Configure mariadb" 11 | ansible.builtin.include_tasks: 12 | file: configure_mariadb.yml 13 | 14 | - name: "Secure mariadb" 15 | ansible.builtin.include_tasks: 16 | file: secure_mariadb_nginx.yml 17 | -------------------------------------------------------------------------------- /roles/lemp_stack/tasks/secure_mariadb_nginx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #Run an equivalent of mysql_secure_install (see https://stackoverflow.com/questions/25136498/ansible-answers-to-mysql-secure-installation) 3 | - name: "Set the root password" 4 | community.mysql.mysql_user: 5 | user: root 6 | password: "{{ mysql_root_password }}" 7 | host: localhost 8 | 9 | - name: "Create a /root/.my.cnf file" 10 | ansible.builtin.template: 11 | src: my.cnf.j2 12 | dest: /root/.my.cnf 13 | owner: root 14 | group: root 15 | mode: '0700' 16 | 17 | - name: Removes all anonymous user accounts 18 | community.mysql.mysql_user: 19 | name: '' 20 | host_all: yes 21 | state: absent 22 | 23 | - name: "Secure the MySQL root user" 24 | community.mysql.mysql_user: 25 | user: "root" 26 | password: "{{ mysql_root_password }}" 27 | host: "{{item}}" 28 | loop: 29 | - "::1" 30 | - "127.0.0.1" 31 | - "localhost" 32 | - "{{ ansible_fqdn }}" 33 | 34 | - name: "Remove nginx default file" 35 | file: 36 | path: "/etc/nginx/sites-enabled/default" 37 | state: absent 38 | -------------------------------------------------------------------------------- /roles/lemp_stack/templates/my.cnf.j2: -------------------------------------------------------------------------------- 1 | [client] 2 | user = root 3 | password = {{ mysql_root_password }} 4 | -------------------------------------------------------------------------------- /roles/letsencrypt_challenge/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "nginx reload" 3 | systemd: 4 | name: nginx 5 | state: reloaded 6 | -------------------------------------------------------------------------------- /roles/letsencrypt_challenge/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install packages" 3 | apt: 4 | name: "{{item}}" 5 | state: present 6 | loop: 7 | - python3-certbot-nginx 8 | 9 | - name: "Generate RSA 4096 key" 10 | openssl_privatekey: 11 | path: "/etc/pki/cert/private/{{item}}.key" 12 | type: RSA 13 | size: 4096 14 | loop: 15 | - "account" 16 | - "domain" 17 | 18 | - name: "Generate an OpenSSL Certificate Signing Request" 19 | openssl_csr: 20 | path: "/etc/pki/cert/csr/{{nextcloud_instance_name}}.{{your_domain}}.csr" 21 | privatekey_path: "/etc/pki/cert/private/domain.key" 22 | common_name: "{{nextcloud_instance_name}}.{{your_domain}}" 23 | 24 | - name: "Configure nginx for nextcloud instance" 25 | template: 26 | src: acme.conf.j2 27 | dest: "/etc/nginx/sites-available/{{nextcloud_instance_name}}.{{your_domain}}.acme.conf" 28 | notify: "nginx reload" 29 | 30 | - name: "Create a link in sites-enabled" 31 | file: 32 | src: "/etc/nginx/sites-available/{{nextcloud_instance_name}}.{{your_domain}}.acme.conf" 33 | dest: "/etc/nginx/sites-enabled/{{nextcloud_instance_name}}.{{your_domain}}.acme.conf" 34 | state: link 35 | 36 | - name: Force all notified handlers to run at this point, not waiting for normal sync points 37 | meta: flush_handlers 38 | 39 | - name: "Create a challenge for {{nextcloud_instance_name}}.{{your_domain}} using a account key from a variable." 40 | acme_certificate: 41 | account_key_src: /etc/pki/cert/private/account.key 42 | acme_directory: "https://acme-v02.api.letsencrypt.org/directory" 43 | acme_version: 2 44 | account_email: "{{admin_email}}" 45 | terms_agreed: yes 46 | csr: "/etc/pki/cert/csr/{{nextcloud_instance_name}}.{{your_domain}}.csr" 47 | dest: "/etc/nginx/ssl/{{nextcloud_instance_name}}.{{your_domain}}.crt" 48 | fullchain_dest: "/etc/nginx/ssl/{{nextcloud_instance_name}}.{{your_domain}}-fullchain.crt" 49 | chain_dest: "/etc/nginx/ssl/{{nextcloud_instance_name}}.{{your_domain}}-intermediate.crt" 50 | register: acme_challenge 51 | 52 | - name: "Create dirs with owner to nginx" 53 | file: 54 | path: "{{item}}" 55 | state: directory 56 | recurse: yes 57 | owner: www-data 58 | group: www-data 59 | loop: 60 | - "/usr/share/nginx/challenges/.well-known/acme-challenge/" 61 | 62 | - name: "Run challenge" 63 | block: 64 | - name: "Copying challenge files" 65 | copy: 66 | dest: "/usr/share/nginx/challenges/{{item.value['http-01'].resource}}" 67 | content: "{{ item.value['http-01'].resource_value }}" 68 | owner: www-data 69 | group: www-data 70 | mode: "0600" 71 | with_dict: "{{ acme_challenge.challenge_data }}" 72 | 73 | - name: "Let the challenge be validated and retrieve the cert and intermediate certificate" 74 | acme_certificate: 75 | account_key_src: /etc/pki/cert/private/account.key 76 | acme_directory: "https://acme-v02.api.letsencrypt.org/directory" 77 | acme_version: 2 78 | account_email: "{{admin_email}}" 79 | terms_agreed: yes 80 | csr: "/etc/pki/cert/csr/{{nextcloud_instance_name}}.{{your_domain}}.csr" 81 | dest: "/etc/nginx/ssl/{{nextcloud_instance_name}}.{{your_domain}}.crt" 82 | fullchain_dest: "/etc/nginx/ssl/{{nextcloud_instance_name}}.{{your_domain}}-fullchain.crt" 83 | chain_dest: "/etc/nginx/ssl/{{nextcloud_instance_name}}.{{your_domain}}-intermediate.crt" 84 | data: "{{ acme_challenge }}" 85 | notify: "nginx reload" 86 | 87 | - name: "Clean challenges files" 88 | file: 89 | path: "/usr/share/nginx/challenges/{{item.value['http-01'].resource}}" 90 | state: absent 91 | with_dict: "{{ acme_challenge.challenge_data }}" 92 | when: acme_challenge.challenge_data is defined 93 | -------------------------------------------------------------------------------- /roles/letsencrypt_challenge/templates/acme.conf.j2: -------------------------------------------------------------------------------- 1 | #See https://docs.nextcloud.com/server/17/admin_manual/installation/nginx.html 2 | server { 3 | listen 80; 4 | listen [::]:80; 5 | server_name {{nextcloud_instance_name}}.{{your_domain}}; 6 | location /.well-known/acme-challenge/ { 7 | alias /usr/share/nginx/challenges/.well-known/acme-challenge/; 8 | try_files $uri =404; 9 | } 10 | # enforce https 11 | location / { 12 | return 301 https://$server_name:{{nextcloud_port}}$request_uri; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /roles/nextcloud_installation/defaults/main.yml: -------------------------------------------------------------------------------- 1 | nextcloud_version: "24.0.7" -------------------------------------------------------------------------------- /roles/nextcloud_installation/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "nginx reload" 3 | systemd: 4 | name: nginx 5 | state: reloaded 6 | -------------------------------------------------------------------------------- /roles/nextcloud_installation/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Download file with check (sha256)" 3 | get_url: 4 | url: "https://download.nextcloud.com/server/releases/nextcloud-{{nextcloud_version}}.zip" 5 | dest: "/tmp/nextcloud-{{nextcloud_version}}.zip" 6 | checksum: "sha256:https://download.nextcloud.com/server/releases/nextcloud-{{nextcloud_version}}.zip.sha256" 7 | 8 | - name: "Install nextcloud" 9 | unarchive: 10 | src: "/tmp/nextcloud-{{nextcloud_version}}.zip" 11 | dest: "/usr/share/nginx/" 12 | remote_src: yes 13 | 14 | - name: "Change /usr/share/nginx/nextcloud owner to nginx" 15 | file: 16 | path: "{{item}}" 17 | state: directory 18 | recurse: yes 19 | owner: www-data 20 | group: www-data 21 | loop: 22 | - "/usr/share/nginx/nextcloud" 23 | 24 | - name: "Create a new database for nextcloud" 25 | mysql_db: 26 | name: nextcloud 27 | state: present 28 | 29 | - name: "Create database user nextcloud with password and nextcloud database privileges and 'WITH GRANT OPTION'" 30 | mysql_user: 31 | name: nextcloud 32 | password: "{{nextcloud_mariadb_password}}" 33 | priv: 'nextcloud.*:ALL,GRANT' 34 | state: present 35 | 36 | - name: "Configure nginx for nextcloud instance" 37 | template: 38 | src: nextcloud.conf.j2 39 | dest: "/etc/nginx/sites-available/{{nextcloud_instance_name}}.{{your_domain}}.conf" 40 | notify: "nginx reload" 41 | 42 | - name: "Create a link in sites-enabled" 43 | file: 44 | src: "/etc/nginx/sites-available/{{nextcloud_instance_name}}.{{your_domain}}.conf" 45 | dest: "/etc/nginx/sites-enabled/{{nextcloud_instance_name}}.{{your_domain}}.conf" 46 | state: link 47 | notify: "nginx reload" 48 | -------------------------------------------------------------------------------- /roles/nextcloud_installation/templates/nextcloud.conf.j2: -------------------------------------------------------------------------------- 1 | #See https://docs.nextcloud.com/server/17/admin_manual/installation/nginx.html 2 | upstream php-handler { 3 | #server 127.0.0.1:9000; 4 | server unix:/run/php/php-fpm.sock; 5 | } 6 | 7 | # Set the `immutable` cache control options only for assets with a cache busting `v` argument 8 | map $arg_v $asset_immutable { 9 | "" ""; 10 | default "immutable"; 11 | } 12 | 13 | server { 14 | listen {{nextcloud_port}} ssl http2; 15 | listen [::]:{{nextcloud_port}} ssl http2; 16 | server_name {{nextcloud_instance_name}}.{{your_domain}}; 17 | 18 | # Path to the root of your installation 19 | root /usr/share/nginx/nextcloud; 20 | 21 | # Use Mozilla's guidelines for SSL/TLS settings 22 | # https://mozilla.github.io/server-side-tls/ssl-config-generator/ 23 | # NOTE: some settings below might be redundant 24 | ssl_certificate /etc/nginx/ssl/{{nextcloud_instance_name}}.{{your_domain}}-fullchain.crt; 25 | ssl_certificate_key /etc/pki/cert/private/domain.key; 26 | 27 | # Prevent nginx HTTP Server Detection 28 | server_tokens off; 29 | 30 | # HSTS settings 31 | # WARNING: Only add the preload option once you read about 32 | # the consequences in https://hstspreload.org/. This option 33 | # will add the domain to a hardcoded list that is shipped 34 | # in all major browsers and getting removed from this list 35 | # could take several months. 36 | #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always; 37 | 38 | # set max upload size and increase upload timeout: 39 | client_max_body_size 512M; 40 | client_body_timeout 300s; 41 | fastcgi_buffers 64 4K; 42 | 43 | # Enable gzip but do not remove ETag headers 44 | gzip on; 45 | gzip_vary on; 46 | gzip_comp_level 4; 47 | gzip_min_length 256; 48 | gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; 49 | 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/wasm 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; 50 | 51 | # Pagespeed is not supported by Nextcloud, so if your server is built 52 | # with the `ngx_pagespeed` module, uncomment this line to disable it. 53 | #pagespeed off; 54 | 55 | # The settings allows you to optimize the HTTP2 bandwitdth. 56 | # See https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/ 57 | # for tunning hints 58 | client_body_buffer_size 512k; 59 | 60 | # HTTP response headers borrowed from Nextcloud `.htaccess` 61 | add_header Referrer-Policy "no-referrer" always; 62 | add_header X-Content-Type-Options "nosniff" always; 63 | add_header X-Download-Options "noopen" always; 64 | add_header X-Frame-Options "SAMEORIGIN" always; 65 | add_header X-Permitted-Cross-Domain-Policies "none" always; 66 | add_header X-Robots-Tag "none" always; 67 | add_header X-XSS-Protection "1; mode=block" always; 68 | 69 | # Remove X-Powered-By, which is an information leak 70 | fastcgi_hide_header X-Powered-By; 71 | 72 | # Specify how to handle directories -- specifying `/index.php$request_uri` 73 | # here as the fallback means that Nginx always exhibits the desired behaviour 74 | # when a client requests a path that corresponds to a directory that exists 75 | # on the server. In particular, if that directory contains an index.php file, 76 | # that file is correctly served; if it doesn't, then the request is passed to 77 | # the front-end controller. This consistent behaviour means that we don't need 78 | # to specify custom rules for certain paths (e.g. images and other assets, 79 | # `/updater`, `/ocm-provider`, `/ocs-provider`), and thus 80 | # `try_files $uri $uri/ /index.php$request_uri` 81 | # always provides the desired behaviour. 82 | index index.php index.html /index.php$request_uri; 83 | 84 | # Rule borrowed from `.htaccess` to handle Microsoft DAV clients 85 | location = / { 86 | if ( $http_user_agent ~ ^DavClnt ) { 87 | return 302 /remote.php/webdav/$is_args$args; 88 | } 89 | } 90 | 91 | location = /robots.txt { 92 | allow all; 93 | log_not_found off; 94 | access_log off; 95 | } 96 | 97 | # Make a regex exception for `/.well-known` so that clients can still 98 | # access it despite the existence of the regex rule 99 | # `location ~ /(\.|autotest|...)` which would otherwise handle requests 100 | # for `/.well-known`. 101 | location ^~ /.well-known { 102 | # The rules in this block are an adaptation of the rules 103 | # in `.htaccess` that concern `/.well-known`. 104 | 105 | location = /.well-known/carddav { return 301 /remote.php/dav/; } 106 | location = /.well-known/caldav { return 301 /remote.php/dav/; } 107 | 108 | location /.well-known/acme-challenge { try_files $uri $uri/ =404; } 109 | location /.well-known/pki-validation { try_files $uri $uri/ =404; } 110 | 111 | # Let Nextcloud's API for `/.well-known` URIs handle all other 112 | # requests by passing them to the front-end controller. 113 | return 301 /index.php$request_uri; 114 | } 115 | 116 | # Rules borrowed from `.htaccess` to hide certain paths from clients 117 | location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; } 118 | location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; } 119 | 120 | # Ensure this block, which passes PHP files to the PHP process, is above the blocks 121 | # which handle static assets (as seen below). If this block is not declared first, 122 | # then Nginx will encounter an infinite rewriting loop when it prepends `/index.php` 123 | # to the URI, resulting in a HTTP 500 error response. 124 | location ~ \.php(?:$|/) { 125 | # Required for legacy support 126 | rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri; 127 | 128 | fastcgi_split_path_info ^(.+?\.php)(/.*)$; 129 | set $path_info $fastcgi_path_info; 130 | 131 | try_files $fastcgi_script_name =404; 132 | 133 | include fastcgi_params; 134 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 135 | fastcgi_param PATH_INFO $path_info; 136 | fastcgi_param HTTPS on; 137 | 138 | fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice 139 | fastcgi_param front_controller_active true; # Enable pretty urls 140 | fastcgi_pass php-handler; 141 | 142 | fastcgi_intercept_errors on; 143 | fastcgi_request_buffering off; 144 | 145 | fastcgi_max_temp_file_size 0; 146 | } 147 | 148 | location ~ \.(?:css|js|svg|gif|png|jpg|ico|wasm|tflite|map)$ { 149 | try_files $uri /index.php$request_uri; 150 | add_header Cache-Control "public, max-age=15778463, $asset_immutable"; 151 | access_log off; # Optional: Don't log access to assets 152 | 153 | location ~ \.wasm$ { 154 | default_type application/wasm; 155 | } 156 | } 157 | 158 | location ~ \.woff2?$ { 159 | try_files $uri /index.php$request_uri; 160 | expires 7d; # Cache-Control policy borrowed from `.htaccess` 161 | access_log off; # Optional: Don't log access to assets 162 | } 163 | 164 | # Rule borrowed from `.htaccess` 165 | location /remote { 166 | return 301 /remote.php$request_uri; 167 | } 168 | 169 | location / { 170 | try_files $uri $uri/ /index.php$request_uri; 171 | } 172 | } 173 | --------------------------------------------------------------------------------