YGH

[홈서버 만들기] 10. 클라우드 스토리지

2020년 11월 17일

개요

Nextcloud는 Dropbox, Google Drive, Onedrive 처럼 웹이나 앱으로 저장소를 접근해 네트워크로 파일을 관리할 수 있게 해주는 프로그램이다.

Nextcloud는 그러한 서비스를 직접 홈서버에 설치하여 자신의 하드디스크 용량 만큼 저장소 용량으로 사용할 수 있어서 매우 좋다.

홈서버 만들기 제일 처음에 이 부분을 중점으로 둔 제품으로 NAS가 있다고 소개한 적이 있다.

굳이 NAS가 아니더라도 우분투 운영체제에 Nextcloud를 설치해서 사용해도 충분히 좋다.

무료임에도 불구하고 개인 정보 보호 규정과 보안이 매우 잘 되어 있으며, 접속 정책도 완벽하게 제어할 수 있다.

준비물

앞에서 다룬 아래 사항들이 준비되어 있어야한다.

php 설치 및 설정

Nextcloud 공식 홈페이지에서 알려주는 운용에 필요한 모듈들을 설치하자.

만약 WordPress를 설치했었다면 php-fpm 설치는 건너뛰면 된다.

$ sudo apt install php-fpm
$ sudo apt install php-imagick php-common php-mysql php-fpm php-gd php-json php-curl php-zip php-xml php-mbstring php-bz2 php-intl

Nextcloud에 대용량 파일을 업로드 하기위해 다음 값을 변경해주자.

post_max_size = 4G
upload_max_filesize = 4G

데이터베이스 생성

$ sudo mysql -u root -p
> CREATE DATABASE nextcloud DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
> CREATE USER nc@localhost identified by '[비밀번호]';
> GRANT ALL PRIVILEGES ON nextcloud.* to nc@localhost;
> FLUSH PRIVILEGES;
> exit

Nextcloud 다운로드

Nextcloud 공식 홈페이지에 접속해서 최신버전 다운로드 주소를 알아내자.

https://nextcloud.com/install/

복사할 링크를 토대로 다운로드 및 압축을 풀어보자.

$ cd ~
$ wget [복사한 링크 주소]
$ tar -xvf nextcloud-19.0.3.tar.bz2
$ sudo mv nextcloud /var/www
$ sudo chown -R www-data:www-data /var/www/nextcloud/

DNS 레코드 추가

호스팅 케이알에서 DNS 레코드로 cloud.ygh.kr 을 추가하자.

기존 DNS 레코드와 동일하게 A레코드로써 IP주소를 가리키게 하면 된다.

Nginx 서버 블록 생성

아래 내용은 Nextcloud 공식 홈페이지의 docs를 토대로 만들어 보았고 정상 작동 하는 것을 확인했다.

서버 블록 내용 중 가장 많으므로 자세히 확인하여 자신의 도메인 주소만 정확히 수정하자.

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

server {
        listen 80;
        server_name cloud.ygh.kr;
        return 301 https://$server_name$request_uri;
}

server {
        listen 443 ssl http2;
        server_name cloud.ygh.kr;
        root /var/www/nextcloud;

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

        ssl_certificate /etc/letsencrypt/live/ygh.kr/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/ygh.kr/privkey.pem;
        ssl_trusted_certificate /etc/letsencrypt/live/ygh.kr/chain.pem;
        ssl_dhparam /etc/ssl/dhparam.pem;
        ssl_session_timeout 10m;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets off;

        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256;
        ssl_ecdh_curve secp384r1;
        ssl_stapling on;
        ssl_stapling_verify on;

        add_header Strict-Transport-Security max-age=15552000;
        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;
        fastcgi_hide_header X-Powered-By;

        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 { }

        client_max_body_size 0;
        fastcgi_buffers 64 4K;

        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/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;

        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\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) {
                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;
                fastcgi_param front_controller_active true;
                fastcgi_pass php-handler;
                fastcgi_intercept_errors on;
                fastcgi_request_buffering off;
        }
        location ~ ^/(?:updater|oc[ms]-provider)(?:$|\/) {
                try_files $uri/ =404;
                index index.php;
        }
        location ~ ^/.+[^\/]\.(?:css|js|woff2?|svg|gif|map)$ {
                try_files $uri /index.php$request_uri;
                add_header Cache-Control "public, max-age=15778463";
                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;

                access_log off;
        }
        location ~ ^/.+[^\/]\.(?:png|html|ttf|ico|jpg|jpeg|bcmap)$ {
                try_files $uri /index.php$request_uri;
                access_log off;
        }
}

문법에 이상이 없는지 검사 후 Nginx를 재시작 해서 새로운 서버 블록을 적용시키자.

$ sudo ln -s /etc/nginx/sites-available/cloud.ygh.kr /etc/nginx/sites-enabled/
$ sudo nginx -t
$ sudo service nginx restart

Nextcloud 저장소 폴더 생성

우선 데이터를 저장할 하드디스크가 마운트까지 되어있는지 확인해보자.

필자의 홈서버에서는 1.84TiB의 용량을 가진 디스크가 확인된다.

$ sudo fidsk -l
...
Disk /dev/sdb: 1.84 TiB, 2000398934016 bytes, 3907029168 sectors
Disk model: ST2000LM003 HN-M
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

마운트가 되어있는지 확인해보자.

/dev/sdb 디스크가 /mnt/sdb에 마운트 되어있는 것을 확인할 수 있다.

아니 근데 아무것도 없는데 왜 77M가 사용 중인지 모르겠다.

$ df -h
...
/dev/sdb        1.8T   77M  1.7T   1% /mnt/sdb
...

이 과정에서 안된 것이 있을 경우 아래 순서대로 따라하면 된다.

  1. 홈서버에 하드디스크를 추가한다.
  2. sudo fdisk -l 명령어로 추가된 하드디스크를 확인한다.
    /dev/sda, /dev/sdb, … 과 같이 마지막 알파벳이 a부터 z까지 바뀌며 표기될 것이다.
  3. sudo fdisk /dev/sda 명령어로 디스크를 파티션 포맷한 뒤 마운트까지 해야 하는데 이 부분부터는 인터넷에 ‘우분투 하드디스크 추가하기’ 이런 식으로 검색해서 따라하는 것을 추천한다.

그럼 HDD에 저장소 폴더를 생성해보자.

$ cd /mnt/sdb
$ sudo mkdir nextcloud-data
$ sudo chown -R www-data:www-data nextcloud-data

Nextcloud 설치

웹 브라우저를 통해 자신만의 홈서버 Nextcloud 주소 https://cloud.[도메인] 로 접속하고 아래를 따라하자.

  1. Nextcloud 접속 때 관리자 계정을 만든다.
  2. 방금 만든 저장소 폴더를 데이터 폴더로 지정해준다.
  3. 생성한 데이터베이스의 정보를 입력해준다.
    특별한 경우가 아니면 포트번호는 기본값 3306이므로 굳이 지정해주지 않아도 된다.
  4. 아래쪽에 보면 Install recommended apps 라고 달력, 연락처 등의 앱들을 자동으로 설치한다고 되어있는데 지금 당장 필요한 것도 아니고 다음에 필요할 때 설치하면 되니 체크 해제하고 설치 완료 버튼을 누르자.

잘 접속된 것을 확인할 수 있다.

보이는 파일과 폴더들은 기본으로 만들어진 것들이니 전부 삭제해주면 된다.

Nextcloud 보완

아직 Nextcloud가 완벽하게 안정화되지 않았을 것이다.

이를 보충해줘야 하는데 Nextcloud가 자체적으로 필요한 부분을 알려준다.

웹 페이지 오른쪽 위의 톱니바퀴 부분 또는 사용자 이미지를 눌러 설정으로 들어가자.

왼쪽 메뉴들 중 [개요]에 들어가면 [보안 및 설치 경고] 란을 확인할 수 있다.

여러가지 경고 메시지가 보이는데 필자와 상황이 다를 수 있으니 그 점은 구글링을 통해 해결하길 바란다.

먼저, PHP에서 시스템 환경 변수를 조회할 수 있게 하자.

아래 항목들이 제일 앞에 세미콜론으로 주석처리 된 것을 없애주자.

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

PHP 메모리 제한을 높여주자.

memory_limit = 512M

X-Frame-Options 과 Strict-Transport-Security 헤더를 설정해주자.

필자는 설치 당시 Strict-Transport-Security 가 오타가 났어서 게시글 내용은 수정해 두었기에 이 부분을 건드릴 필요는 없다.

...
        add_header Strict-Transport-Security max-age=15552000;
        add_header X-Content-Type-Options nosniff;
        add_header X-Frame-Options "SAMEORIGIN" always;
        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;
        fastcgi_hide_header X-Powered-By;
...

메모리 캐시를 구성해보자.

먼저, OPcache를 설치해야 하는데 php 5.5 버전 이후에 자동으로 설치된다고 하니 확인만 해보자.

$ ls /etc/php/7.4/fpm/conf.d/10-opcache.ini

이제부터 설정값을 변경할 것인데 마찬가지로 명령모드에서 /opc 를 입력해서 검색하면 되고 세미콜론으로 주석처리 되어있는 것을 풀고 값을 입력하자.

[opcache]
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
opcache.max_wasted_percentage=5
opcache.validate_timestamps=1
opcache.revalidate_freq=2

메모리 캐시에 필요한 APCu와 성능 향상에 도움되는 php 모듈들을 추가적으로 설치하자.

$ sudo apt install php-apcu php-bcmath php-gmp

설치할 APCu 모듈을 Nextcloud에 반영하자.

아래는 옵션 설명이다.

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

변경된 내용들을 적용하기 위해 서비스들을 재시작하자.

$ sudo service php7.4-fpm restart
$ sudo service nginx restart

웹 페이지로 접속해서 확인해보자.

성능 검사에 통과함으로써 이제 안정적으로 Nextcloud를 사용할 수 있게 되었다.

fail2ban 적용

마지막으로 보안을 위해 fail2ban을 이용해서 로그인 횟수 초과 시 일정 시간 로그인 불가 상태를 만들자.

Nextcloud는 여러 번 로그인 실패했을 경우 수 초 또는 수 분 후 다시 시도하게끔 자체적으로 기능을 지원한다.

이 정도 보안으로 만족하는 사람은 굳이 fail2ban을 적용할 필요는 없다.

하지만 필자는 안전을 위해 SSH와 같이 5번 실패했을 경우 1일동안 서버 접근을 못하게끔 막아버릴 것이다.

Nextcloud 공식 홈페이지에서 제공하는 필터를 사용해서 jail을 만들어 보자.

로그인 실패한 항목에 대한 필터링 설정을 하는것이다.

[Definition]
_groupsre = (?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*)
failregex = ^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Login failed:
            ^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Trusted domain error.
datepattern = ,?\s*"time"\s*:\s*"%%Y-%%m-%%d[T ]%%H:%%M:%%S(%%z)?"

Nextcloud에 대한 fail2ban 규칙을 설정하자.

fail2ban을 설치할 때 maxretry, bantime, findtime을 jail.local에 기본값으로 설정하였기 때문에 여기서 굳이 지정해줄 필요가 없다.

logpath는 자신의 nextcloud의 데이터가 저장된 폴더에 있는 로그파일을 적어준다.

[nextcloud]
backend = auto
enabled = true
port = 80,443
protocol = tcp
filter = nextcloud
logpath = /mnt/sdb/nextcloud-data/nextcloud.log

fail2ban의 변경 내용을 적용하기 위해 서비스를 재시작하고 적용되었는지 확인해보자.

$ sudo service fail2ban restart
$ sudo fail2ban-client status nextcloud
Status for the jail: nextcloud
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- File list:        /mnt/sdb/nextcloud-data/nextcloud.log
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:

이제 테스트를 위해 스마트폰의 모바일 데이터 환경에서 비밀번호를 5번 이상 틀려보자.

그리고 상태를 확인해보면 벤 당한 것을 확인할 수 있다.

$ sudo fail2ban-client status nextcloud
Status for the jail: nextcloud
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     5
|  `- File list:        /mnt/sdb/nextcloud-data/nextcloud.log
`- Actions
   |- Currently banned: 1
   |- Total banned:     1
   `- Banned IP list:   223.39.149.171

이제 마음 편하게 클라우스 스토리지 Nextcloud를 사용하면 된다.

필자가 기존에 사용하던 OneDrive가 마이크로소프트에서 제작된 클라우드 스토리지라서 웹기반 문서작업 기능이 있었는데 매우 편리했었다.

Nextcloud로 넘어오면서 이러한 기능을 사용하고 싶었는데 어머나 세상에! 이미 있는 데다가 Nextcloud와 연동해서 사용할 수 있다.

다음 시간에 Nextcloud에서 웹기반으로 문서 작업(워드, 파워포인트, 엑셀) 작업을 할 수 있는 OnlyOffice를 설치해보도록 하자.