背景

私がお世話になっている某サークルにおいて、サークルメンバー間においてファイルを共有するためにDropboxを使っていたそう。ただ、容量が不足したとのことで、サーバーを設置してファイルを共有するという選択をしたそう。当初はSambaを使っていたが、もちろんサークルメンバーはローカルネットワーク外にもいる。SMBをグローバルに直接晒すのは非常に危険なのは承知で、fail2ban使ってある程度不要な接続を拒否して運用していた。ただ、どうもネットワークによってはTCP445向けの通信を拒否するよう。これ自体は納得の処置ではあるので、TCP Port 445のSMBを使わずにファイルを共有する方法を探す必要があった。
※どうしても接続できない場合は、sftpを使っていたが、サーバーのフォルダ構成上相性が悪く、使いにくいものとなっていた。

聞くところによると、SMB over QUICというプロトコルが一部で実装されているとのこと。名の通りSMBをQUICを使って実現する実装。QUICというのは、UDPとTLSを組み合わせて使われるトランスポート層のプロトコル。http/3のトランスポート層として使われていることからもグローバルネットワークへの接続に対しても問題なさそう。
ただ、このプロトコルがSambaに実装されて利用できるのはまだ先になりそうなのと、サークルメンバーの多くはWindows10を使っており、執筆時点において利用できなさそう。

そこでいくつかのファイル共有サービスを探していたところ、表題のNextcloudに出会った。https(http)の上で実現されるサービスであり、SMBのような専用の通信が不要。P2Pでもないので、大学などP2P禁止のネットワークからの接続にも十分対応可能。よさそうなので導入してみる。

平文httpでの通信は行わないように構成する。TCP Port 80はLet's encryptのHTTP-01チャレンジ専用とし、TCP Port 443を使用する。

公式ドキュメント(参考資料)

英語のみではあるが、しっかりとしたドキュメントが用意されている。

Nextcloud Documentation

注意事項(追伸)

この記事では Nextcloud 25系 をインストールしました。最新版ではこの内容では不十分なことがあります。公式ドキュメントをご確認ください。

用意するもの

httpsを実現するために、Let's Encryptの証明書を使用します。これには、ドメインが必要です。DDNSでも多くの場合は利用できます。

  • サーバーマシン
  • ドメイン
  • グローバルIPアドレス ポート443、80

完全にドメインの管理下である場合はDNS-01認証が利用できるので、TCP Port 80はなくても良い。

サーバーOS選定 (Ubuntu Server 22.04 LTS)

NextcloudはWebサーバー上で動くサービスのため、サービスを動かすためのWebサーバーを動かすためのOSを選定する。ドキュメントには、Example installationとして下記のOSを使う方法が紹介されていた。

  • Ubuntu 22.04 LTS
  • CentOS 8
  • OpenBSD

私は以前Ubuntu Serverを使っていたことがあるので、今回はUbuntu Server 22.04 LTSを選定した。

少々逸れるが、選定したハードが古く、Ubuntu Server の live版 のインストーラーでインストールしても、BIOSがOSを見つけられず起動できなかった。適切なパーティションを構成できていなかったのかもしれない。Live版ではない旧式のインストーラーが利用できる最後のバージョン18.04から20.04を経由して22.04までアップグレードした。

サーバーはmdadmを使用してソフトウェアRAID1(2TB×2)でセットアップした。
ブートローダーを両方のディスクにインストールし、片方のストレージからでもブートできるように構成した。

Webサーバー選定 (nginx)

公式にサポートされているのがApache 2.xのみで、それ以外のWebサーバーはサポートしていないと明記されている。

個人的にApache2を使うメリットはphp_moduleが使えることだと思っているが、Apache2をhttp/2で使うとphp_moduleは使えずphp-fpmを使用する必要がある。そうなるとただ重い(偏見)Webサーバーを使う必要がなく、さらに今回使用するサーバーは計算能力が貧弱なため、なるべく軽いWebサーバーを使用したいと考えた。

nginxの設定が有志により公式ドキュメント向けに記載されていて、設定しきる自信があったので、今回はnginxを選定した。

パッケージインストール

OSのインストールと、sshなど管理系の環境が整った前提から書き始める。
OSインストールとアップデート直後を想定しており、既にほかのWebサイトを運用している場合は参考程度にお願いします。

インストールするパッケージは以下の通り

Let's Encrypt ACME クライアント、Webサーバー、データベース、php8.1、redis

sudo apt install certbot nginx mariadb-server php-fpm redis-server php-gd php-mysql php-curl php-mbstring php-intl php-gmp php-bcmath php-xml php-zip php-apcu php-redis

公式ドキュメントは php-imagick をインストールするように書いてありますが、致命的な脆弱性がちょくちょく見つかっており、Nextcloudにおいて優先度が低いパッケージであることから、snap版ではインストールされていない。なので、今回はsnap版に倣ってインストールしないことにする。

TCP Port 80のListen停止

TCP Port 80 は証明書発行するためのHTTP-01専用にする。nginxがデフォルトで使用しているので、使用しないように設定して適用する。

sudo rm /etc/nginx/sites-enabled/default
sudo systemctl restart nginx

証明書発行

Let's Encrypt の証明書を発行します。
cloud.example.comのところには、用意したドメインを入力してください。

sudo certbot certonly --standalone -d cloud.example.com

証明書が更新されたときに、nginxを再起動します。

sudo vi /etc/letsencrypt/renewal-hooks/post/nginx.sh
#!/bin/bash
systemctl restart nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/post/nginx.sh

cronで証明書を自動更新するように設定します。Let's Encryptの証明書は90日間有効なので、月に1回くらい実行してあれば十分です。

sudo vi /etc/crontab
44 5 15 * * root certbot renew

データベースの設定

Nextcloudで使用するデータベースを設定します。

sudo mysql

データベースに下記を入力します。
usernameとpasswordは、各自置き換えてください。

CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
GRANT ALL PRIVILEGES ON nextcloud.* TO 'username'@'localhost';
FLUSH PRIVILEGES;
quit;

Webサーバーの設定

nginxのconfigを書きます。

cloud.example.comの部分は、用意したドメイン名に変更してください。
php8.1の部分は、各自インストールしたバージョンに変更する必要があるかもしれません。
TLSv1.3のみの接続にすることでセキュアにしています。古い端末は接続できないかもしれません。

sudo vi /etc/nginx/sites-available/nextcloud
upstream php-handler {
    #server 127.0.0.1:9000;
    server unix:/var/run/php/php8.1-fpm.sock;
}

# Set the `immutable` cache control options only for assets with a cache busting `v` argument
map $arg_v $asset_immutable {
    "" "";
    default "immutable";
}

server {
    listen 443      ssl http2;
    listen [::]:443 ssl http2;
    server_name cloud.example.com;

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

    # Use Mozilla's guidelines for SSL/TLS settings
    # https://mozilla.github.io/server-side-tls/ssl-config-generator/
    ssl_certificate     /etc/letsencrypt/live/cloud.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cloud.example.com/privkey.pem;

    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers on;

    # Prevent nginx HTTP Server Detection
    server_tokens off;

    # HSTS settings
    # WARNING: Only add the preload option once you read about
    # the consequences in https://hstspreload.org/. This option
    # will add the domain to a hardcoded list that is shipped
    # in all major browsers and getting removed from this list
    # could take several months.
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;

    # set max upload size and increase upload timeout:
    client_max_body_size 512M;
    client_body_timeout 300s;
    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/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;

    # Pagespeed is not supported by Nextcloud, so if your server is built
    # with the `ngx_pagespeed` module, uncomment this line to disable it.
    #pagespeed off;

    # The settings allows you to optimize the HTTP2 bandwitdth.
    # See https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/
    # for tunning hints
    client_body_buffer_size 512k;

    # HTTP response headers borrowed from Nextcloud `.htaccess`
    add_header Referrer-Policy                      "no-referrer"   always;
    add_header X-Content-Type-Options               "nosniff"       always;
    add_header X-Download-Options                   "noopen"        always;
    add_header X-Frame-Options                      "SAMEORIGIN"    always;
    add_header X-Permitted-Cross-Domain-Policies    "none"          always;
    add_header X-Robots-Tag                         "none"          always;
    add_header X-XSS-Protection                     "1; mode=block" always;

    # Remove X-Powered-By, which is an information leak
    fastcgi_hide_header X-Powered-By;

    # Specify how to handle directories -- specifying `/index.php$request_uri`
    # here as the fallback means that Nginx always exhibits the desired behaviour
    # when a client requests a path that corresponds to a directory that exists
    # on the server. In particular, if that directory contains an index.php file,
    # that file is correctly served; if it doesn't, then the request is passed to
    # the front-end controller. This consistent behaviour means that we don't need
    # to specify custom rules for certain paths (e.g. images and other assets,
    # `/updater`, `/ocm-provider`, `/ocs-provider`), and thus
    # `try_files $uri $uri/ /index.php$request_uri`
    # always provides the desired behaviour.
    index index.php index.html /index.php$request_uri;

    # Rule borrowed from `.htaccess` to handle Microsoft DAV clients
    location = / {
        if ( $http_user_agent ~ ^DavClnt ) {
            return 302 /remote.php/webdav/$is_args$args;
        }
    }

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

    # Make a regex exception for `/.well-known` so that clients can still
    # access it despite the existence of the regex rule
    # `location ~ /(\.|autotest|...)` which would otherwise handle requests
    # for `/.well-known`.
    location ^~ /.well-known {
        # The rules in this block are an adaptation of the rules
        # in `.htaccess` that concern `/.well-known`.

        location = /.well-known/carddav { return 301 /remote.php/dav/; }
        location = /.well-known/caldav  { return 301 /remote.php/dav/; }

        location /.well-known/acme-challenge    { try_files $uri $uri/ =404; }
        location /.well-known/pki-validation    { try_files $uri $uri/ =404; }

        # Let Nextcloud's API for `/.well-known` URIs handle all other
        # requests by passing them to the front-end controller.
        return 301 /index.php$request_uri;
    }

    # Rules borrowed from `.htaccess` to hide certain paths from clients
    location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)  { return 404; }
    location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console)                { return 404; }

    # Ensure this block, which passes PHP files to the PHP process, is above the blocks
    # which handle static assets (as seen below). If this block is not declared first,
    # then Nginx will encounter an infinite rewriting loop when it prepends `/index.php`
    # to the URI, resulting in a HTTP 500 error response.
    location ~ \.php(?:$|/) {
        # Required for legacy support
        rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri;

        fastcgi_split_path_info ^(.+?\.php)(/.*)$;
        set $path_info $fastcgi_path_info;

        try_files $fastcgi_script_name =404;

        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $path_info;
        fastcgi_param HTTPS on;

        fastcgi_param modHeadersAvailable true;         # Avoid sending the security headers twice
        fastcgi_param front_controller_active true;     # Enable pretty urls
        fastcgi_pass php-handler;

        fastcgi_intercept_errors on;
        fastcgi_request_buffering off;

        fastcgi_max_temp_file_size 0;

        proxy_send_timeout 300;
        proxy_read_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
    }

    location ~ \.(?:css|js|svg|gif|png|jpg|ico|wasm|tflite|map)$ {
        try_files $uri /index.php$request_uri;
        add_header Cache-Control "public, max-age=15778463, $asset_immutable";
        access_log off;     # Optional: Don't log access to assets

        location ~ \.wasm$ {
            default_type application/wasm;
        }
    }

    location ~ \.woff2?$ {
        try_files $uri /index.php$request_uri;
        expires 7d;         # Cache-Control policy borrowed from `.htaccess`
        access_log off;     # Optional: Don't log access to assets
    }

    # Rule borrowed from `.htaccess`
    location /remote {
        return 301 /remote.php$request_uri;
    }

    location / {
        try_files $uri $uri/ /index.php$request_uri;
    }
}
sudo ln -s /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/nextloud 

php-fpm の設定

sudo vi /etc/php/8.1/fpm/pool.d/www.conf

コメント解除

clear_env = no

デフォルト0確認

; request_terminate_timeout = 0
sudo vi /etc/php/8.1/fpm/php.ini

下記パラメータを変更

upload_max_filesize = 384M
post_max_size = 448M
memory_limit = 512M
max_execution_time = 180
session.save_handler = redis
session.save_path = "tcp://localhost:6379"

Nextcloud のダウンロードとインストール

ファイルをダウンロードします。

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

ファイルが破損していないことをチェックします。OKと表示が出ることを確認してください。

md5sum -c latest.tar.bz2.md5
sha256sum -c latest.tar.bz2.sha256

PGP署名を検証します。

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

ファイルを展開します。

tar -xjvf latest.tar.bz2
sudo cp -r nextcloud /var/www
sudo chown -R www-data:www-data /var/www/nextcloud
sudo mkdir /var/www/nextcloud_data
sudo chown -R www-data:www-data /var/www/nextcloud_data

Nextcloudの定期実行ジョブを追加します。

sudo vi /etc/crontab
*/5 *	* * *	www-data	php --define apc.enable_cli=1 -f /var/www/nextcloud/cron.php

nginxとphp-fpmを再起動して行った設定を反映します。
これを行うと、初期設定画面が表示されますので、素早く管理者アカウントの作成を行ってください。次の項で説明します。

sudo systemctl restart php8.1-fpm
sudo systemctl restart nginx

Nextcloudの設定

Webブラウザで https://cloud.example.com/ を開きます。

管理者アカウント(ユーザー名・パスワード)を指定します。

データフォルダーは下記を指定します。

/var/www/nextcloud_data

データベースのユーザー名とデータベースのパスワードは、"データベースの設定"で設定したユーザー名とパスワードを入力してください。

データベース名は下記を指定します。

nextcloud

データベースの接続先は以下を指定します。

localhost:3306

「推奨アプリをインストール」は無しでもよいです。あとからインストールできます。
「セットアップを完了します」をクリックして完了です。少し時間がかかります。

Nextcloudの設定ファイルを編集します。
前後の入力ルールを見ながら、適切な場所に追加してください。

sudo vi /var/www/nextcloud/config/config.php
  'default_phone_region' => 'JP',
  'memcache.local' => '\OC\Memcache\APCu',
  'memcache.distributed' => '\OC\Memcache\Redis',
  'memcache.locking' => '\OC\Memcache\Redis',
  'redis' => [
    'host' => 'localhost',
    'port' => 6379,
  ],

完了

導入完了です。
管理者設定から概要を確認し、不足している設定を実施してください。
ログを確認すると、発生しているエラーが確認できます。