リバースプロキシを使用したVPSサーバの設定をする

# はじめに
仕事の関係でテストサーバが必要となり、VPSサーバをレンタルしました。その設定過程を記載します。
私が求める条件としては、Rocky Linux 8.10が使用出来る、Node.jsがインストール出来る、ドメインのネームサーバの変更が出来るというものでした。

# レンタルサーバ環境
[xServerさんのVPS](https://vps.xserver.ne.jp/)をレンタルすることにしました。
仕事で使用しているサーバがRed Hat Enterprise Linux release 8.10 (Ootpa)ですので、それと互換性があるRocky Linux 8.10を選択します。

OS: Rocky Linux 8.10(64bit)
メモリ：　2GB
ストレージ(NVMe SSD): 50GB
node version: v16.20.2 ※nvmで管理
npm version: 8.19.4

# サーバにログイン
サーバ契約時に作成した公開鍵は、サーバの
/root/.ssh/authorized_keys
にあります。

サーバ契約時に作成した秘密鍵は自身のPCの以下に保存しています。
~/.ssh/xServerKey.pem

[こちら](https://vps.xserver.ne.jp/support/manual/man_server_port.php
)のxServerさんのマニュアルを参考にして、SSHの接続を許可します。ポート番号はデフォルトの22のままにします。

![スクリーンショット 2025-02-06 11.43.52.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/8c779e8f-5661-cbf9-c901-01bf9756fc06.jpeg)
※後述する通り、最終的にはフィルタールール設定は使用せず、ファイアウォールで対応することにしました。

以下のコマンドでログイン出来ます。
```
ssh -i ~/.ssh/xServerKey.pem root@（IPアドレス）
```

-i ~/.ssh/xServerKey.pem で使用する秘密鍵を指定しています。

# ユーザの作成
以下のコマンドでユーザーを作成します。

```
adduser newusername
```

以下のコマンドでパスワードを設定します。
```
passwd newusername
```

### ssh接続のための設定

作成したユーザでもssh接続を可能とするために、.ssh ディレクトリを作成します。
```
sudo mkdir -p /home/newusername/.ssh
```
ここで-pは、親ディレクトリが存在しない場合、自動で作成 するオプションです。

次に、.ssh ディレクトリとその中のファイルの所有者（ユーザー）とグループを newusername に変更します。
```
sudo chown -R newusername:newusername /home/newusername/.ssh
```

契約時にサーバー側で作成された 公開鍵 (/root/.ssh/authorized_keys) を、newusername の .ssh ディレクトリにコピーし、権限の変更などをします。
```
sudo cp /root/.ssh/authorized_keys /home/newusername/.ssh/authorized_keys
sudo chown newusername:newusername /home/newusername/.ssh/authorized_keys
sudo chmod 600 /home/newusername/.ssh/authorized_keys
```

これで以下のコマンドでログイン出来るようになりました。
```
ssh -i ~/.ssh/xServerKey.pem newusername@（IPアドレス）
```

### sudoコマンドを使用するための設定
デフォルトでは新しいユーザはsudoを実行する権限を持っていないので与えます。

```
usermod -aG wheel newusername
```

usermod：ユーザーの情報を変更するコマンド
-a：(append) 追加（既存のグループ設定を維持しながら追加）
-G：wheel	管理者権限（sudo）を持つユーザーが所属する特別なグループであるwheelグループに追加

これでsudoが使用出来るようになりました。

# ファイアウォールの設定
### firewalld をインストール
```
sudo dnf install -y firewalld
```

### firewalld を有効化
```
sudo systemctl enable --now firewalld
```

### FTP のポートを開放
```
sudo firewall-cmd --permanent --add-service=ftp
sudo firewall-cmd --permanent --add-port=40000-50000/tcp
sudo firewall-cmd --reload
```
上記のコマンドは以下の意味を持ちます。
•	--add-service=ftp → FTPの標準ポート (21) を開放。
•	--add-port=40000-50000/tcp → パッシブモードで使うポート範囲を開放。
•	--reload → 設定を反映。

### HTTP (80) と HTTPS (443) などのポートを許可
```
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
```

ついでによく使用する、3000、8080、8443のポートも許可しておきます。
```
sudo firewall-cmd --permanent --add-port=3000/tcp
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --permanent --add-port=8443/tcp
```


### 設定の反映
設定を反映させるには、firewalld をリロードします。
```
sudo firewall-cmd --reload
```

再度、開放されたポートやサービスを確認します。
```
sudo firewall-cmd --list-all
```



:::note info
最初は、xServerのパケットフィルター設定をONにしていましたが、後述するFileZillaの使用で「ディレクトリリスト表示の取り出しに失敗しました」というエラーが発生したので、OFFとすることにしました。
![スクリーンショット 2025-02-07 10.25.58.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/2cb6126c-7d16-3d55-ec73-45e71fd5c0be.jpeg)
:::



# FTP設定
ローカルからサーバにファイルなどを転送するためにFTP設定をします。

## FTP サーバーのインストール
```
sudo dnf install -y vsftpd
```

## vsftpd の設定ファイルを編集
```
sudo vi /etc/vsftpd/vsftpd.conf
```
として、設定ファイルを開き編集していきます。


```
listen=YES（IPv4を有効化）
listen_ipv6=NO（IPv6を無効化）
```

```
pasv_enable=YES（クライアントが「PASV」コマンドを送ると、サーバーがデータ転送用のポートを開放し、クライアントにそのポート番号を通知する。）
pasv_min_port=40000
pasv_max_port=50000
```
を追記しました。

```
chroot_local_user=YES（すべてのユーザーを chroot で制限）
chroot_list_enable=YES	（chroot_list に記載されたユーザーのみ chroot 解除）
chroot_list_file=/etc/vsftpd/chroot_list   （chroot_listファイル）
allow_writeable_chroot=YES	（エラー防止用。書き込み可能なディレクトリでもログインできるように）
```

/etc/vsftpd/chroot_listを作成してchrootを解除したユーザ名を記載します。
```
sudo touch /etc/vsftpd/chroot_list
sudo chmod 600 /etc/vsftpd/chroot_list
echo "newusername" | sudo tee -a /etc/vsftpd/chroot_list
```

### SELinux の設定
FTP サーバーがホームディレクトリにアクセスすることを許可
```
sudo setsebool -P ftpd_full_access on
```

FTP を通じたファイルアップロードを許可
```
sudo setsebool -P allow_ftpd_full_access on
```

### SELinux が有効な場合の FTP パッシブモードの設定
SELinux が Enforcing になっている場合、パッシブモードの FTP を許可する必要があります。
```
sudo setsebool -P ftpd_use_passive_mode on
```
•	-P オプション → 設定を永続化（リブート後も有効）
•	ftpd_use_passive_mode on → FTP のパッシブモードを許可

※SELinux（Security-Enhanced Linux）
Linux システムに強固なアクセス制御を追加するセキュリティ機能です。
Red Hat Enterprise Linux（RHEL）や Rocky Linux などのディストリビューション で標準搭載されています。



### vsftpd を起動 & 自動起動設定
```
sudo systemctl start vsftpd
sudo systemctl enable vsftpd
```
2つのコマンドは、FTP サーバー vsftpd (Very Secure FTP Daemon) の起動と自動起動の設定を行うものです。
vsftpd は FTP サーバーのデーモン（バックグラウンドで動作するプロセス）です。
systemctl enable は systemd の機能で、サービスを 永続的に有効化 します。
これにより、サーバーを再起動しても vsftpd が自動的に起動するようになります。


### FTPコマンドのインストール
```
sudo dnf install -y ftp
```

サーバ上で以下を実行すると、つながることが確認できます。
```
ftp localhost
```

### クライアントから FTP に接続

```
ftp （IPアドレス）
```
として、ユーザ名、パスワードを入力すると、クライアントからサーバにつながります。

## FileZillaを使う
以下の設定でつながりました。

![スクリーンショット 2025-02-07 11.18.19.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/79642e06-66eb-15ed-e852-f6aff77ace2d.jpeg)

ホストは、まだドメインを取得していないので、IPアドレスを記載しています。


# nvmのインストール
ユーザでサーバにログインします。
[こちらの記事](https://qiita.com/k96mz/items/e33f493be15e9253e2ff#nvm%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)を参考にインストールします。

# node.jsのインストール

nvmを利用して、開発環境のホスティングサーバに合わせて
node version: v16.20.2
をインストールします。

```
nvm install v16.20.2
```

ちなみにnpmのバージョンは8.19.4がインストールされています。


# Nginxのインストール
リバースプロキシを使用したいので、Nginxをインストールします。
問題意識としては、Node.jsのexpressを使用してサーバを立ち上げる時に、HTTPS接続だと「(failed) net::ERR_HTTP2_PROTOCOL_ERROR 」というエラーが頻発していました。これをどうにか解決したいと思い、リバースプロキシを使用する方法を試してみます。
リバースプロキシとは、クライアントとリバースプロキシ間はHTTPSを利用し、リバースプロキシとサーバ間はHTTPを利用するというものです。
こうすることで、HTTP/2プロトコル対応などはリバースプロキシに任せられるので、上記エラーが解消されると期待しています。

### システムを最新の状態に更新
```
sudo dnf update -y
```

### EPEL リポジトリを有効化
Rocky Linux のデフォルトリポジトリには nginx が含まれていますが、最新版を取得するために EPEL (Extra Packages for Enterprise Linux) リポジトリ を追加するのが一般的です。
```
sudo dnf install -y epel-release
```

### Nginx のインストール
```
sudo dnf install -y nginx
```

インストール後、Nginx を起動し、OS 起動時に自動起動するように設定します。
```
# Nginx の起動
sudo systemctl start nginx

# Nginx を自動起動するように設定
sudo systemctl enable nginx

# Nginx の状態を確認
sudo systemctl status nginx
```

```
http://<サーバーのIPアドレス>
```
とすると確認できます。

![スクリーンショット 2025-02-07 14.27.43.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/3be0e9e8-b765-eb6e-09bf-2ef5665758fa.jpeg)


# Let's Encryptを利用したSSL/TLS 証明書の設定

### [Let's Encrypt](https://letsencrypt.org/)とは？
無料でSSL/TLS証明書を発行する認証局（CA: Certificate Authority） です。Let’s Encryptの証明書は 90日ごとに更新する必要がありますが、Certbot（Let’s Encrypt公式ツール）を使うと、自動更新 できるので手間がかかりません。

こちらの[cerbotのページ](https://certbot.eff.org/instructions?ws=nginx&os=snap)で作業をします。
### Cerbotとは？
Let’s Encrypt が提供する 無料のSSL/TLS証明書 を取得・管理するためのツールで、Nginxの設定を自動で変更、証明書の自動更新が可能などの特徴があります。

My HTTP website is running [Nginx] on [Linux (snap)]で作業します。
snapは、Certbotの公式推奨インストール方法です。

## 設定手順
### SSH into the server
sudo権限を持ったユーザで、SSH接続を利用してサーバに入ります。

### Install snapd
snapdは、“Snap パッケージ” を管理するデーモン（バックグラウンドサービス） です。
通常、Linux ではソフトウェアをインストールする方法として dnf（RHEL, CentOS, Rocky Linux）や apt（Ubuntu, Debian）を使います。
しかし、これらの方法ではディストリビューションごとに異なるパッケージ管理システムがあり、互換性の問題が発生することがあります。

Snap は、その問題を解決するために設計された新しいパッケージ形式 です。

```
# まず EPEL リポジトリを有効化
sudo dnf install epel-release -y

# `snapd` をインストール
sudo dnf install snapd -y

# `snapd` を有効化
sudo systemctl enable --now snapd

# `snapd` の状態を確認
systemctl status snapd
```

### Remove certbot-auto and any Certbot OS packages
dnfなどのcerbotパッケージが存在すると、うまく動かないようなので、念の為削除しておきます。

```
certbot --version
```
こちらのコマンドでcerbotが見つからなかったので、OKです。

### Install Certbot

多少詰まりましたが、chatGPTに聞いてインストール出来ました。

### Prepare the Certbot command

```
sudo ln -s /snap/bin/certbot /usr/bin/certbot
```
このコマンドは /snap/bin/certbot へのシンボリックリンク（ソフトリンク）を /usr/bin/certbot に作成するものです。
シンボリックリンク（-s オプション）は、ファイルやディレクトリへの 参照（ショートカット）を作成する 仕組みです。
この場合、/usr/bin/certbot を実行すると、実際には /snap/bin/certbot が呼び出されます。
このコマンドを実行することで、certbot を どこからでも実行できるようにする ことができます！

### Choose how you'd like to run Certbot
```
sudo certbot --nginx
```
コマンドを実行後、質問をいくつかされるのでそれに回答すると終了です。

### Test automatic renewal
証明書が自動更新するかテストします。
```
sudo certbot renew --dry-run
```

成功したので、実際の更新も問題なく動作するはずです。

### Confirm that Certbot worked
最後にhttpsでページが見られるか確認しました。


# nginxの設定ファイル
/etc/nginx/nginx.conf
に設定ファイルがあり、証明書の保存場所やリダイレクト設定などが記載されています。
以下のように設定して、解説も記載しました。cerbotによって設定されたものから、少し修正しています。

```conf:nginx.conf
user nginx;
worker_processes auto; # サーバーの CPU コア数に応じて最適なプロセス数を自動設定
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;  #	1つのワーカープロセスが同時に処理できる最大の接続数 を設定
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    # アクセスログのフォーマットを定義
    
    access_log  /var/log/nginx/access.log  main;

    sendfile            on; #ファイル送信の最適化
    tcp_nopush          on; #レスポンスの送信最適化
    tcp_nodelay         on; #リアルタイム応答の高速化
    keepalive_timeout   65; #コネクションの維持時間
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types; #適用される MIMEタイプ
    default_type        application/octet-stream; #MIME タイプが不明なファイルは application/octet-stream（バイナリデータ）として送信

    include /etc/nginx/conf.d/*.conf; # 設定の分割管理だが使用していない

    server {
        listen       80 default_server; #ポート 80（HTTP）で待ち受ける。
        listen       [::]:80 default_server; #IPv6 アドレスでも 80 を待ち受ける。#「default_server」はserver_name にも一致しないリクエストをこの server ブロックで処理
        server_name  k96mz.com;
        root         /usr/share/nginx/html; #Web サイトのルートディレクトリを指定

        include /etc/nginx/default.d/*.conf; #追加の設定を分割管理できるが使用していない。

        location / {　# /（ルートディレクトリ）に対するリクエストを処理
        }

        error_page 404 /404.html; #404 エラーが発生した場合、/404.html を表示
            location = /40x.html { #/40x.html にリクエストがあったときの処理を指定
        }

        error_page 500 502 503 504 /50x.html; #500（内部エラー）、502（Bad Gateway）、503（Service Unavailable）、504（Gateway Timeout）の場合、/50x.html を表示
            location = /50x.html {
        }
    }

    server {
        listen       443 ssl http2 default_server;
        listen       [::]:443 ssl http2 default_server;
        server_name  k96mz.com;
        root         /usr/share/nginx/html;

            ssl_certificate /etc/letsencrypt/live/k96mz.com/fullchain.pem; # managed by Certbot
            ssl_certificate_key /etc/letsencrypt/live/k96mz.com/privkey.pem; # managed by Certbot

            include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
            ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

        include /etc/nginx/default.d/*.conf;
        
       #HTTP でバックエンドに転送
        location / {
            proxy_pass http://localhost:8080;
            proxy_set_header Host $host; #クライアントのホスト名を保持
            proxy_set_header X-Real-IP $remote_addr; #クライアントの IP アドレスを保持
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #プロキシを経由した元の IP アドレスを送信
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
```

ここで肝になるのは、リバースプロキシ設定部分です。

例えば、
https\://k96mz.com/test/index.html
にきたリクエストは、
http\://localhost:8080/test/index.html
に転送されます。
この時、リダイレクトとは異なり、クライアントに表示されるURLは、
https\://k96mz.com/test/index.html
のままです。

設定後は以下でエラーがないか確認します。
```
sudo nginx -t
```

コードを修正した後は、以下のコマンドで修正を反映します。

```
sudo systemctl reload nginx
```

## node.jsでサーバを立ち上げる
node.jsでサーバを立ち上げて、実際の挙動を確かめました。

```javascript
const express = require('express');
const app = express();
const PORT = 8080;

// ルート（"/"）にアクセスしたときのレスポンス
app.get('/', (req, res) => {
    res.send('Hello, World!');
});

app.get('/test', (req, res) => {
  res.send('Test response');
});

// サーバーを起動
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});
```

実際に
https\://k96mz.com
とすると「Hello, World!」が返り、
https\://k96mz.com/test
とすると、「Test response」が返ります。

# リバースプロキシを使用してベクトルタイルを表示
[こちらの記事](https://qiita.com/k96mz/items/e33f493be15e9253e2ff)で、node.jsを用いてベクトルタイルサーバを作成しました。HTTPSでの地図表示について、非常に不安定な状態で、おそらくですがHTTP/2プロトコルでの表示がうまくいっていないようでした。そこで、今回はリバースプロキシを用いて、リバースプロキシとサーバ間はHTTP接続とすることで、不安定な状態を解決したいと思います。
使用するコードはこちらの[GitHubページ](https://github.com/k96mz/20241025hostingPracticeServerRockyLinux)にあります。

### コード修正
今回のサーバに合わせて以下の点を修正します。
・使用するポートは8080
・styleO-pbf.jsonのタイルURLは以下に変更
"tiles": ["https://k96mz.com/VTpractice/{z}/{x}/{y}.pbf"]
・styleO-mbtiles.jsonのタイルURLは以下に変更
"tiles": ["https://k96mz.com/VT/zxy/VTpracticegzip/{z}/{x}/{y}.pbf"]

上記の設定に修正することで、例えば、以下のURLにアクセスすると地図が表示されます。（現在はサーバを立ち上げていないので、地図は見られません。）
https://k96mz.com/map/mapO-mbtiles.html

![スクリーンショット 2025-02-12 11.28.02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/c6158afc-9795-41e7-8ace-2fc4824585d2.jpeg)

コードの中で、appHttps.jsなどはHTTPS接続のための鍵を読み込んでいますが、リバースプロキシを利用すれば、そのようなことは意識しなくて良いので楽だと感じました

# まとめ
レンタルしたVPSサーバの設定過程を記載しました。特にNginxを使用したリバースプロキシの挙動を試したかったのですが、地図表示がうまくいって良かったです。

# Reference

https://qiita.com/k96mz/items/e33f493be15e9253e2ff

https://github.com/k96mz/20241025hostingPracticeServerRockyLinux