OPCache :: Increase WordPress, NextCloud Performance

워드프레스나 NextCloud는 PHP 기반으로 돌아가게 되는데, NextCloud 12 Server Tuning 글에 보면 Enable PHP OpCache 란 섹션이 있다.

나머지 http2, MariaDB 사용 까지는 ‘제로부터 시작하는 NextCloud 설치하기‘  에 있고,  Redis-based Transactional File Locking 도 적용되어 있다. (나중에 기회되면 설명하려고 한다.)

그래서 남은 항목인 OPCache 에 대해 정리하려고 한다.

OPCache란?

OPcache는 PHP script 를 bytecode로 컴파일한 후에 공유 메모리에 저장하여 성능을 향상시키는 모듈이다.
5.5 이상부터 PHP에 내장되어 있는데, 설정 방법은 php.ini 파일을 열어서 아래 구문들을 추가해주면 된다.

경로는 /etc/php/7.0/fpm/php.ini이다.

opcache.enable=1
opcache.enable_cli=1
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=1

각각 구문에 대한 설명은 아래와 같다.

  • enable : OPCache 활성화 여부
  • enable_cli : CLI 버전의 PHP에서 OPCache 활성화 여부
  • interned_strings_buffer : 내부 문자열을 저장할 때 사용되는 메모리 양(MB), PHP 5.3.0 이상만 적용됨
  • max_accelerated_files : OPCache 해시 테이블의 최대 키 수 (= 스크립트 수)를 적는다. 사용되는 실제 값은 리스트 (223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987) 에서 설정한 값과 같거나 큰 수 중 첫번째 숫자가 된다. 최소는 200이고, 최대는 PHP 5.5.6 미만에서는 100,000 이고, 그 이상은 1.000.000 이다.
  • memory_consumption : OPCache 가 사용하는 공유 메모리 저장 영역의 크기 (MB)
  • save_comments: false면 모든 주석이 opcache 캐시에서 삭제되어 Doctrine, Zend Framework 2 와 같은 주석 구문에 의존하는 기능이 작동하지 않음
  • revalidate_freq: 타임스탬프 기준에서 어느 시간을 기점으로 업데이트 할건지 결정함

적용 후 서비스 재적용을 해준다.

sudo systemctl restart php7.0-fpm.service

 

제로부터 시작하는 NextCloud 설치하기 on VPS

NextCloudDropbox 와 비슷하게 파일 호스팅 서비스를 제공하는 솔루션인데, 개인 서버에도 설치할 수 있다.

오늘 퇴근하고 나서 약 5시간 동안 삽질 끝에 설치에 성공했는데, 겸사겸사 정리도 해보려고 한다.

환경

OS는 Ubuntu 16.04 LTS 를 사용할 것이며, 아래 조건을 만족해야 한다. (예제에서는 16.10을 사용한다.)

  1. SSH 접속 가능한 상태
  2. 터미널에 두려움을 가지지 않는 용기
  3. 설명이 없어도 알아서 구글링하는 용기

목적

제목대로, 제로부터 NextCloud 구축까지 시행할 것이다.

즉, 아무것도 없는 상태에서 Nginx, PHP-FPM, MariaDB, Let’s Encrypt SSL 연결, NextCloud 까지 전부 설정을 마칠 것이다.

작업 예상시간은 1시간 이하이다.

참고로, [ ] 로 감싸진 것의 경우 자기가 원하는 정보로 교체하면 된다.

0. VPS 구매하기

VPS 는 웹호스팅과 서버호스팅의 중간 형태로, 하나의 물리 서버를 여러 개의 가상 서버로 나누어 사용하면서 각각의 가상 서버를 독립적으로 운영할 수 있게 하는 것이다.

여기서는 Vultr 에 있는 UzukiLive 서버에서 진행한다. 옵션은 아래와 같다.

NextCloud를 어느정도까지 사용할 것에 따라 다르지만, 보통 2 CPU 에 16GB 이상이 150명까지 환경에 적당하다고 한다.

Deployment Recommendation 에 어느정도 나와있으니 자기의 목적에 맞는 VPS 를 찾아보자.

나는 적어도 혼자나 많아봤자 5~6명일거라서, 기존에 $5 옵션 이었던 1 CPU 1GB 에서 1 CPU 2GB 로 증설했다.

1. 루트 유저 추가하기

초기 서버 설정으로 관리용 계정을 root 이외에 하나 더 만드는 것이다.

마음에 드는 터미널로 접속하자.

ssh root@***.***.***.***

비밀번호는 Vultr 기준 인스턴스 메인에 있다.

adduser [pyxis]

유저를 추가하는 명령어이다.

그러면 비밀번호와 정보를 물어볼텐데, 적당히 채워주고 생성을 마친다.

usermod -aG sudo [pyxis]

그리고 방금 추가한 유저에게 root 권한을 부여한다.

이렇게 해서 루트 유저 추가가 끝났고, 만든 유저로 로그인하자.

ssh pyxis@***.***.***.***

이런 메세지가 표시되면 성공한 것이다.

2. Nginx

Nginx Web Server 로, Apache 를 쓸 수도 있었지만 개인적으로는 Nginx 가 좀 더 친숙했으므로 Nginx 를 사용하려고 한다.

sudo apt-get -y install nginx

위 명령어로 Nginx 를 설치한다.

한 가지 팁으로, sudo 를 일일히 치기 귀찮다면 sudo -i로 root 모드로 들어간 상태에서 진행할 수 있다.

설치를 다 한다음, sudo nano /etc/nginx/nginx.conf를 입력해서 에디터를 연다.

user www-data;
worker_processes 8;
pid /run/nginx.pid;
 
events {
    worker_connections 768;
    # multi_accept on;                                                                                                                                                                                                                   
}
 
http {
 
    ##                                                                                                                                                                                                                                   
    # Basic Settings                                                                                                                                                                                                                     
    ##                                                                                                                                                                                                                                   
 
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off;                                                                                                                                                                                                                 
 
    # server_names_hash_bucket_size 64;                                                                                                                                                                                                  
    # server_name_in_redirect off;                                                                                                                                                                                                       
 
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

worker_processes 값과 server_tokens 값을 변경하는데, 각각 설명은 다음과 같다.

  • worker_processes: 하나의 worker 가 동시에 실행할 수 있는 Thread 수이다.
  • server_tokens: 서버 버전 정보 표시 금지

참고로 nano 의 사용법을 몰라도 이 세개만 기억하면 된다.

  • 방향키: 이동, 백스페이스: 지우기
  • control + K : 라인 지우기
  • control + X : 저장

변경한 후, Nginx 의 서비스를 재시작해주자.

sudo systemctl restart nginx.service

3. NextCloud 다운로드

현재 최신버전은 12.0.3 이다.

cd /var/www
sudo wget https://download.nextcloud.com/server/releases/nextcloud-12.0.3.zip
unzip nextcloud-12.0.3.zip
rm -rf nextcloud-12.0.3.zip

이런 구조로 되면 된다.

4. NextCloud 폴더에 권한 설정

sudo adduser nextcloud
sudo chown -R nextcloud:www-data /var/www/nextcloud
sudo chmod -R o-rwx /var/www/nextcloud

nextcloud 유저를 만든 다음, 해당 유저 및 www-data 그룹을 방금 압축을 푼 /var/www/nextcloud 에 권한을 설정한다.

5. PHP-FPM 설정

5.6 에 비해 7.0에서 좀 더 php의 속도가 증가했으므로 7.0을 설치하기를 추천한다.

sudo apt-get -y install php-cli php-json php-curl php-imap php-gd php-mysql php-xml php-zip php-intl php-mcrypt php-imagick php-mbstring
sudo apt-get install -y php-fpm

설치가 다 되면 /etc/php/7.0/fpm/pool.d 폴더에 nextcloud 전용 설정 파일을 만든다.

[nextcloud]
listen = /var/run/nextcloud.sock
 
listen.owner = nextcloud
listen.group = www-data
 
user = nextcloud
group = www-data
 
pm = ondemand
pm.max_children = 30
pm.process_idle_timeout = 60s
pm.max_requests = 500
 
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.0-fpm.service

6. MariaDB 설치

sudo apt-get install -y mariadb-server mariadb-client

MySQL 를 사용해도 상관없다.

$ sudo mysql_secure_installation
 
Set root password? [Y/n] Y
Remove anonymous users? [Y/n] Y
Disallow root login remotely? [Y/n] Y
Remove test database and access to it? [Y/n] Y
Reload privilege tables now? [Y/n] Y

7. Nextcloud 데이터베이스 설정

sudo mysql -u root -p
MariaDB> CREATE DATABASE nextcloud;
Query OK, 1 row affected (0.00 sec)

MariaDB> CREATE USER "nextcloud"@"localhost";
Query OK, 0 rows affected (0.00 sec)
 
MariaDB> SET password FOR "nextcloud"@"localhost" = password('[PASSWORD]');
Query OK, 0 rows affected (0.00 sec)
 
MariaDB> GRANT ALL PRIVILEGES ON nextcloud.* TO "nextcloud"@"localhost" IDENTIFIED BY "[PASSWORD]";
Query OK, 0 rows affected (0.00 sec)
 
MariaDB> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
 
MariaDB> EXIT
Bye

PASSWORD 는 마지막 NextCloud 설정할 때 필요하니 잘 기억해두자.

8. Nginx 설정 파일 만들기

사실 하나하나 추가해야 되지만, 여기서는 완전한 파일만 기재한다.

/etc/nginx/sites-available 폴더에 nextcloud 란 이름으로 파일을 만든다.

upstream php-handler {

    server unix:/var/run/nextcloud.sock;
}

server {

    listen 80;
    listen [::]:80;
    server_name [cloud.uzuki.live];

    root /var/www/nextcloud/;

    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 Strict-Transport-Security 'max-age=31536000; includeSubDomains;';

    location = /robots.txt {

        allow all;
        log_not_found off;
        access_log off;
    }

    location = /.well-known/carddav {

        return 301 $scheme://$host/remote.php/dav;
    }

    location = /.well-known/caldav {

        return 301 $scheme://$host/remote.php/dav;
    }

    client_max_body_size 512M;
    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$uri;
    }

    location ~ ^/.well-known/acme-challenge/* {

        allow all;
    }

    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/.+)\.php(?:$|/) {

        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param modHeadersAvailable true;
        fastcgi_param front_controller_active true;
        fastcgi_pass php-handler;
        fastcgi_intercept_errors on;
        fastcgi_request_buffering off;
    }

    location ~ ^/(?:updater|ocs-provider)(?:$|/) {

        try_files $uri/ =404;
        index index.php;
    }

    location ~* \.(?:css|js|woff|svg|gif)$ {

        try_files $uri /index.php$uri$is_args$args;
        add_header Cache-Control "public, max-age=7200";
        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;

        access_log off;
    }

    location ~* \.(?:png|html|ttf|ico|jpg|jpeg)$ {

        try_files $uri /index.php$uri$is_args$args;
        access_log off;
    }
}
sudo ln -s /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/nextcloud
nginx -t

심볼릭 링크를 생성하고 설정 파일이 문법에 맞는지 확인한다.

이와 같이 나온다면 재시작을 해주자.

sudo systemctl restart nginx.service
sudo systemctl restart php7.0-fpm.service

9. Let’s Encrypt로 SSL 인증받기

이 작업을 위해서는 도메인이 필요하다. NextCloud 는 SSL 이하 사용을 권장하므로 Self-Signing 라도 해야되지만 SSL 인증서를 구매하거나 적어도 Let’s Encrypt 로 인증을 거쳐야 한다.

sudo apt-get install -y software-properties-common
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install -y certbot
sudo certbot certonly --webroot -w /var/www/nextcloud --agree-tos --email [pyxis@uzuki.live] -d [cloud.uzuki.live] --rsa-key-size 4096

인증이 완료되었다고 나오면 dhparams 키도 추가로 생성해주자.

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
sudo chmod 600 /etc/ssl/certs/dhparam.pem

참고로 꽤나 오래 걸리므로 커피 한잔 마시고 오는 것이 좋다.

10. SSL 설정 반영하기

/etc/nginx/sites-available/nextcloud 를 수정한다.

Let’s Encrypt 로 받은 pem 키와 위에서 생성한 dhparam 키를 각각 넣어준다.

upstream php-handler {

    server unix:/var/run/nextcloud.sock;
}

server {

    listen 80;
    listen [::]:80;
    server_name [cloud.uzuki.live];
    return 301 https://$server_name$request_uri;
}

server {

    listen 443 ssl;
    listen [::]:443 ssl;
    server_name [cloud.uzuki.live];

    root /var/www/nextcloud/;

    ssl on;
    ssl_certificate /etc/letsencrypt/live/[cloud.uzuki.live]/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/[cloud.uzuki.live]/privkey.pem;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout 1440m;
    ssl_buffer_size 8k;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED';
    ssl_prefer_server_ciphers on;

    ssl_trusted_certificate /etc/letsencrypt/live/cloud.uzuki.live/chain.pem;
    ssl_stapling on;
    ssl_stapling_verify on;

    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 Strict-Transport-Security 'max-age=31536000; includeSubDomains;';

    location = /robots.txt {

        allow all;
        log_not_found off;
        access_log off;
    }

    location = /.well-known/carddav {

        return 301 $scheme://$host/remote.php/dav;
    }

    location = /.well-known/caldav {

        return 301 $scheme://$host/remote.php/dav;
    }

    client_max_body_size 512M;
    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$uri;
    }

    location ~ ^/.well-known/acme-challenge/* {

        allow all;
    }

    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/.+)\.php(?:$|/) {

        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param HTTPS on;
        #Avoid sending the security headers twice
        fastcgi_param modHeadersAvailable true;
        fastcgi_param front_controller_active true;
        fastcgi_pass php-handler;
        fastcgi_intercept_errors on;
        fastcgi_request_buffering off;
    }

    location ~ ^/(?:updater|ocs-provider)(?:$|/) {

        try_files $uri/ =404;
        index index.php;
    }

    location ~* \.(?:css|js|woff|svg|gif)$ {

        try_files $uri /index.php$uri$is_args$args;
        add_header Cache-Control "public, max-age=7200";
        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;
        # Optional: Don't log access to assets
        access_log off;
    }

    location ~* \.(?:png|html|ttf|ico|jpg|jpeg)$ {

        try_files $uri /index.php$uri$is_args$args;
        access_log off;
    }
}
sudo systemctl reload nginx.service

위 설정들은 SSL Test를 A+ 로 통과할 수 있는 설정이나 필요한 조건에 따라 더 추가할 수 있다.

11. 인증서 자동 갱신

Let’s Encrypt 는 무료인 대신 90일 마다 갱신을 해줘야 한다. crontab 로 자동으로 갱신하도록 설정해준다.

crontab -e
42 23 * * 1 /usr/bin/certbot renew >> /var/log/le-renew.log

12. http2

기존 http 1.1 을 개선한 http의 새로운 프로토콜로, 여러 방법을 사용하여 지연 시간을 감소시킨 프로토콜이다.

위 11번의 설정 파일에서 16 ~ 17줄 부분에 교체만 해주면 된다.

listen 443 ssl http2;
listen [::]:443 ssl http2;

13. 타임아웃 설정

1. /etc/php/7.0/fpm/pool.d/nextcloud.conf 맨 밑 request_terminate_timeout = 300 추가

2. /etc/nginx/sites-available/nextcloud 의 105번째 줄 밑에 fastcgi_read_timeout 300; 추가

14. 접속

마지막으로 server_name 로 설정한 주소로 들어가보면 설정 마법사가 나온다.

이미지 출처: https://www.pcextreme.nl/community/d/153-set-up-your-own-cloud-storage-within-minutes-using-nextcloud-and-aurora-s3

맨 위 두개 필드에는 아이디와 비번을, 그 다음에는 파일을 보관할 장소를 적는다.

마지막 4개 필드에는 7번에서 설정한 데이터베이스 정보를 넣는데, 각각 유저 이름, 비밀번호, 데이터베이스 이름, 주소를 넣는다.

만일 finish setup 에서 넘어가지 않다면 맨 밑 localhost 를 localhost:3306 으로 변경하면 되는 것 같다.

15. 끝

총 15단계에 걸쳐서 설치가 끝났다.

추가적으로 OPCache, redis 를 설치하면 더 속도가 상승되지만 그건 나중에 언급해도 괜찮을 것 같다.