利用 Ubuntu 20.04 + LEMP Stack + Nextcloud 搭建私人云盘的教程

曾经我们在 Ubuntu 18.04 上面搭建过 Nextcloud 服务器,当时采用的 Web 服务器是 Caddy 和 Apache2。这次,我们把系统升级到 Ubuntu 20.04,顺便把 Web 服务器更改为 Nginx,同样利用 Nextcloud 重新搭建私人云盘。下面介绍一下部署的流程。

1 安装 Nginx

逐行运行以下代码,安装、启动 Nginx Web 服务器,并将其设为随系统启动:

sudo apt update
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx

(非必需)检查是否正常运行:

sudo systemctl status nginx

(非必需)更新 Nginx 至最新版本:

# 添加源
echo "deb http://nginx.org/packages/mainline/ubuntu `lsb_release -cs` nginx" \
    | sudo tee /etc/apt/sources.list.d/nginx.list
curl -o /tmp/nginx_signing.key https://nginx.org/keys/nginx_signing.key
sudo mv /tmp/nginx_signing.key /etc/apt/trusted.gpg.d/nginx_signing.asc

# 更新
sudo apt update
sudo apt install nginx -y

# 如果在配置 php 过程中出现问题,需要检查用户是否检查“/etc/nginx/nginx.conf”配置中的用户是否在更新过程中出现了变更。
user www-data;

2 安装 PHP

安装 PHP 及 Nextcloud 依赖的 PHP-FPM 软件包, Ubuntu 20.04 默认支持的 PHP 版本是 v7.4:

sudo apt install php-fpm php-curl php-cli php-mysql php-gd php-common php-xml php-json php-intl php-pear php-imagick php-dev php-common php-mbstring php-zip php-soap php-bz2 php-bcmath php-gmp -y

修改 /etc/php/7.4/fpm/php.ini,取消“date.timezone =”行的注释,并添加自己的时区代码,以上海为例:

date.timezone = Asia/Shanghai

修改 /etc/php/7.4/cli/php.ini,取消“cgi.fix_pathinfo=1”行的注释,并将值改成“0”:

cgi.fix_pathinfo=0

修改 /etc/php/7.4/fpm/pool.d/www.conf,取消下列五行代码的注释:

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

逐行运行以下代码,重启 PHP,并将其设为随系统启动:

sudo systemctl restart php7.4-fpm
sudo systemctl enable php7.4-fpm

(非必需)检查PHP-FPM服务:

sudo ss -xa | grep php
sudo systemctl status php7.4-fpm

3 安装MariaDB

逐行运行以下代码,安装 MariaDB,并将其设为随系统启动:

sudo apt install mariadb-server mariadb-client -y
sudo systemctl enable mariadb

(非必需)检查 MariaDB 是否正常运行:

sudo systemctl status mariadb

配置 MariaDB:

sudo mysql_secure_installation

在配置界面,按提示进行配置:除第一步为 root 用户设置登录密码外,一路按“Enter”进行配置即可。

逐行运行以下代码,创建 Nextcloud 所需的数据库信息(其中,代码中的“nextcloud”是数据库名称,“nextclouduser”为数据库用户名,“your-password”为数据库用户的密码,请自行修改替换):

sudo mysql
create database nextcloud;
create user nextclouduser@localhost identified by 'your-password';
grant all privileges on nextcloud.* to nextclouduser@localhost identified by 'your-password';
flush privileges;
exit

4 下载Nextcloud文件

(非必需)创建 Nextcloud 安装路径 /var/www,假如该路径已经存在,可以忽略;如果需要自定义其他的路径亦可自行修改:

sudo mkdir /var/www

创建私人云盘的文件保存路径:

sudo mkdir /var/www/data

逐行运行以下代码,将 Nextcloud 安装文件压缩包下载并解压到安装路径下:

cd /var/www
sudo wget https://download.nextcloud.com/server/releases/latest.zip
sudo unzip latest.zip
cd

(非必需)如果安装过程中,系统提示缺少 unzip 组件,需要先行安装,再运行上述的下载、解压缩相关代码:

sudo apt install unzip

为 nextcloud、data 文件夹分配正确的用户及用户组,系统默认的用户及用户组无法正常启动 Nextcloud:

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

修改 /etc/php/7.4/fpm/php.ini,找到 “memory_limit = 128M” 行,修改为:

memory_limit = 512M

重启 PHP 服务:

sudo systemctl restart php7.4-fpm

5 为 Nextcloud 配置 Nginx

在 /etc/nginx/conf.d/ 路径下,新建文件“nextcloud.conf”,写入以下内容(需要修改第四行的“example.com”为个人网站域名,假如你所设的安装路径与本文推荐路径不一致,还需要修改第十五行“root /var/www/nextcloud/;”):

upstream php-handler {
    server 127.0.0.1:9000;
    #server unix:/var/run/php/php7.4-fpm.sock;
}

server {
    listen 80;
    listen [::]:80;
    server_name example.com;

    # 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 Referrer-Policy no-referrer;
    add_header Strict-Transport-Security "max-age=15768000;preload;";

    #I found this header is needed on Ubuntu, but not on Arch Linux. 
    add_header X-Frame-Options "SAMEORIGIN";

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

    access_log /var/log/nginx/nextcloud.access;
    error_log /var/log/nginx/nextcloud.error;

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

    # The following 2 rules are only needed for the user_webfinger app.
    # Uncomment it if you're planning to use this app.
    #rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
    #rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json
    # last;

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

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

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

    # Disable gzip to avoid the removal of the ETag header
    gzip off;

    # Uncomment if your server is build with the ngx_pagespeed module
    # This module is currently not supported.
    #pagespeed off;

    error_page 403 /core/templates/403.php;
    error_page 404 /core/templates/404.php;

    location / {
       rewrite ^ /index.php;
    }

    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/.+|core/templates/40[34])\.php(?:$|/) {
       include fastcgi_params;
       fastcgi_split_path_info ^(.+\.php)(/.*)$;
       try_files $fastcgi_script_name =404;
       fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
       fastcgi_param PATH_INFO $fastcgi_path_info;
       #Avoid sending the security headers twice
       fastcgi_param modHeadersAvailable true;
       fastcgi_param front_controller_active true;
       fastcgi_pass unix:/run/php/php7.4-fpm.sock;
       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)$ {
        try_files $uri /index.php$uri$is_args$args;
        add_header Cache-Control "public, max-age=7200";
        # Add headers to serve security related headers (It is intended to
        # have those duplicated to the ones above)
        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 Referrer-Policy no-referrer;
        # Optional: Don't log access to assets
        access_log off;
   }

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

测试 Nginx 配置是否正确,并重新加载 Nginx 使其生效:

sudo nginx -t
sudo systemctl reload nginx

6 安装 Nextcloud

在浏览器地址栏输入个人的网站域名,按提示输入:

  1. 设置管理员帐户(新建)
  2. 设置管理员密码(新建)
  3. 填写数据目录:/var/www/data
  4. 数据库用户:nextclouduser(此前设置好的)
  5. 数据库密码:your-password(此前设置好的)

设置好后,开始安装,等待时间的洗礼。

如果无法正常打开网页,请在防火墙开放 80 端口,顺便也可将 443 端口一并开放,假如你希望进一步申请 SSL 证书的话:

sudo iptables -I INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 443 -j ACCEPT

7 申请 SSL 证书

7.1 方法一:利用 Certbot 申请 RSA 证书

安装 Certbot 及其 Nginx 插件“Python3-certbot-nginx”:

sudo apt install certbot python3-certbot-nginx -y

申请 Let’s Encrypt 证书(替换 “myemail@gmail.com” 为个人电子邮件地址,替换 “example.com” 为个人网站域名):

sudo certbot --nginx --agree-tos --redirect --staple-ocsp --email myemail@gmail.com -d example.com

根据 Certbot 的简单询问,轻松完成证书申请。证书路径一般位于:

/etc/letsencrypt/live/example.com/
fullchain.pem	# 完整的证书链
privkey.pem	# 私钥

查询 Certbot 计时器状态(非必要):

sudo systemctl status certbot.timer

测试 Certbot 是否已经能够自动续订 SSL 证书(非必要):

sudo certbot renew --dry-run

更新 Nginx 配置:

upstream php-handler {
    server 127.0.0.1:9000;
    #server unix:/var/run/php/php7.4-fpm.sock;
}

server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    # enforce https
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com;
	
    # SSL/TLS settings
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.crt;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
	
    # 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 Referrer-Policy no-referrer;
    add_header Strict-Transport-Security "max-age=15768000;preload;";

    #I found this header is needed on Ubuntu, but not on Arch Linux. 
    add_header X-Frame-Options "SAMEORIGIN";

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

    access_log /var/log/nginx/nextcloud.access;
    error_log /var/log/nginx/nextcloud.error;

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

    # The following 2 rules are only needed for the user_webfinger app.
    # Uncomment it if you're planning to use this app.
    #rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
    #rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json
    # last;

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

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

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

    # Disable gzip to avoid the removal of the ETag header
    gzip off;

    # Uncomment if your server is build with the ngx_pagespeed module
    # This module is currently not supported.
    #pagespeed off;

    error_page 403 /core/templates/403.php;
    error_page 404 /core/templates/404.php;

    location / {
       rewrite ^ /index.php;
    }

    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/.+|core/templates/40[34])\.php(?:$|/) {
       include fastcgi_params;
       fastcgi_split_path_info ^(.+\.php)(/.*)$;
       try_files $fastcgi_script_name =404;
       fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
       fastcgi_param PATH_INFO $fastcgi_path_info;
       #Avoid sending the security headers twice
       fastcgi_param modHeadersAvailable true;
       fastcgi_param front_controller_active true;
       fastcgi_pass unix:/run/php/php7.4-fpm.sock;
       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)$ {
        try_files $uri /index.php$uri$is_args$args;
        add_header Cache-Control "public, max-age=7200";
        # Add headers to serve security related headers (It is intended to
        # have those duplicated to the ones above)
        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 Referrer-Policy no-referrer;
        # Optional: Don't log access to assets
        access_log off;
   }

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

测试 Nginx 配置是否正确,并重新加载 Nginx 使其生效:

sudo nginx -t
sudo systemctl reload nginx

7.2 方法二:利用 acme.sh 申请 ECC 证书

acme.sh 的项目地址:https://github.com/acmesh-official/acme.sh

(一)安装 acme.sh:

sudo curl  https://get.acme.sh | sh

(二)创建 一个 bash 的 alias:

alias acme.sh=~/.acme.sh/acme.sh

(三)开启 acme.sh 的自动更新功能:

acme.sh --upgrade --auto-upgrade

(四)申请证书,请自行替换代码中的个人邮箱地址“myemail@gmail.com”及个人网站域名 “example.com”:

acme.sh --set-default-ca --server zerossl
acme.sh --register-account -m myemail@gmail.com
acme.sh --issue -d example.com --nginx --keylength ec-256

(五)复制/安装证书:

此前步骤生成的 SSL 证书无法直接使用,需要将其复制到自定义路径下,才可正常使用:

新建 SSL 证书的转移路径 /var/www/ssl/ :

sudo mkdir /var/www/ssl

复制 fullchain.crt 及 example.com.key 到 /var/www/ssl/ 路径下进行安装:

acme.sh --install-cert -d example.com --ecc --key-file /var/www/ssl/example.com.key --fullchain-file /var/www/ssl/fullchain.crt --reloadcmd "service nginx force-reload"

设置权限,无脑赋权 777 :

sudo chmod -R 777 /var/www/ssl

(六)更新 nextcloud.conf 配置:

修改 /etc/nginx/conf.d/nextcloud.conf 内容:

upstream php-handler {
    server 127.0.0.1:9000;
    #server unix:/var/run/php/php7.4-fpm.sock;
}

server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    # enforce https
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com;
	
    # SSL/TLS settings
    ssl_certificate /var/www/ssl/fullchain.crt;
    ssl_certificate_key /var/www/ssl/example.com.key;
	
    # 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 Referrer-Policy no-referrer;
    add_header Strict-Transport-Security "max-age=15768000;preload;";

    #I found this header is needed on Ubuntu, but not on Arch Linux. 
    add_header X-Frame-Options "SAMEORIGIN";

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

    access_log /var/log/nginx/nextcloud.access;
    error_log /var/log/nginx/nextcloud.error;

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

    # The following 2 rules are only needed for the user_webfinger app.
    # Uncomment it if you're planning to use this app.
    #rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
    #rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json
    # last;

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

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

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

    # Disable gzip to avoid the removal of the ETag header
    gzip off;

    # Uncomment if your server is build with the ngx_pagespeed module
    # This module is currently not supported.
    #pagespeed off;

    error_page 403 /core/templates/403.php;
    error_page 404 /core/templates/404.php;

    location / {
       rewrite ^ /index.php;
    }

    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/.+|core/templates/40[34])\.php(?:$|/) {
       include fastcgi_params;
       fastcgi_split_path_info ^(.+\.php)(/.*)$;
       try_files $fastcgi_script_name =404;
       fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
       fastcgi_param PATH_INFO $fastcgi_path_info;
       #Avoid sending the security headers twice
       fastcgi_param modHeadersAvailable true;
       fastcgi_param front_controller_active true;
       fastcgi_pass unix:/run/php/php7.4-fpm.sock;
       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)$ {
        try_files $uri /index.php$uri$is_args$args;
        add_header Cache-Control "public, max-age=7200";
        # Add headers to serve security related headers (It is intended to
        # have those duplicated to the ones above)
        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 Referrer-Policy no-referrer;
        # Optional: Don't log access to assets
        access_log off;
   }

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

测试 Nginx 配置是否正确,并重新加载 Nginx 使其生效:

sudo nginx -t
sudo systemctl reload nginx

终于部署完成,赶紧去SSL Server Test验收成果吧。

8 参考文献

本文有描述不尽之处,可以参考以下文献:

9 拓展

9.1 安装fail2ban及相关配置

安装fail2ban:

sudo apt update
sudo apt install fail2ban -y

配置fail2ban:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

打开jail.local文件,转到[DEFAULT]部分,按需修改bantime:

# "bantime" is the number of seconds that a host is banned.
bantime  = 6000m

重启fail2ban服务:

sudo systemctl restart fail2ban

查看小黑屋:

sudo fail2ban-client status
sudo fail2ban-client status sshd

发表评论