フレッツ光クロスで自宅サーバーのサイトを公開しよう

はじめに

個人サイトを自宅サーバーから公開するぞ~!と意気込んだところ、そういえば新居の契約を最新のフレッツ光クロスにしていました。IPアドレスはOCNバーチャルコネクトというシステムで割り振られていて、自宅サーバーをサイト公開できるMyDNSの設定が上手く行かなかったので、試行錯誤した奮闘記です。IPv4通信のみになります。

自分の理解の整理の為に、個人サイトを公開する方法いろいろと、インターネットの基本からまとめてみます。

続きを読む

個人サイトを公開する方法色々

SNS周りがごちゃついたために個人サイト回帰の動きもやや出てきていたりしてちょっと楽しいですね。

自分のサイトを公開したいとき、手段はいくつかあります。

既存サービスを利用する

nanoフォレストページ+Wix.comなど、HTMLの知識がなくてもサイトが作成できるサービスが多々あります。HTMLの打てるタイプもあります。

無料で利用できるサイトが多く、簡単にサイトが開設できる分、サイトデザインの手が入れられる部分に限界があったりなどがネックかも。基本的に外部のアプリケーションなどは入れられません。

HTMLも勉強したい場合Github pagesも便利かも。(ただし性的な内容は規約で置けません。)

昔geocitiesはyahooでもなく○○通り何番地という番地制でね…yahoo検索も登録制でね…おばあちゃんご飯は食べたでしょ

レンタルサーバーを借りる

OSやサーバーソフトウェアなどはレンタルサーバー会社側がインストールしてくれているので、中のディレクトリの一部を借りるイメージです。

無料や有料のものがあり、有料のものは機能が豊富で、サブドメインを使ってURLを可愛く出来たり(今までさくらのレンタルサーバーでhttps://estampie.mimoza.jp/を使わせて頂いてました)、WordPressなどの有名ブログソフトは簡単に使用できるようにしている会社も多いです。本当10年以上お世話になりました…。その前はロリポップ!を使っていました。

有料だとPHPは動かせるところも多いので、自分の組んだPHPプログラムなどは使えたりします。フォルダ内の小説のテキストファイルの一覧を読み込んで自動メニュー作成と小説本文もテンプレートを組み合わせて表示する動的小説サイト…みたいなのとかは十分作れます。

DNS設定はサーバー会社側で既に済んでいるので公開も簡単です。

VPSサーバーを借りる

サーバー会社内のつよつよPC内に作られた仮想マシンを借りて、OS(基本はLinux)と色々ソフトを自分でインストールして公開するタイプです。好きなアプリケーションをインストール出来るので、Wordpressを入れてもいいし、Misskeyを入れてもいいし、Minecraft鯖を立てても良いです。レンタルサーバーより自由度が高い分やることも多いですが楽しいです。

「CPU2コア/メモリ1GB/ストレージ50GB」プランや「CPU4コア/メモリ8GB/ストレージ500GB」プランなど、仮想マシンの性能ごとに料金が決められています。Misskeyを入れるなら4GB↑をオススメ。

DNS設定は自分で行う必要がありますが、だいたいサーバー側で簡単設定や解説があるので手順がわかりやすくなっています。

Xserver VPS, さくらのVPS, WebArena, KAGOYA CLOUD VPS, ConoHa VPSなどなど。

クラウドサーバー(IaaS)を借りる

VPSに似ていますが、マシンなどに限らずサービスなどだけ仮想化して借りたりなど、色々種類があります。(IaaS、PaaS、SaaSなどで検索すると色々出てくるかも)(サービスだけ仮想化ならGmailなんかはSaaSの分類)

VPSでメモリ2GBストレージ200GBなど固定で契約していた性能やサイズを、後から増やしたり減らしたりの変更=スケーリングが出来たりするのも特徴です。使った分だけ従量課金や、セキュリティ対策やサイト高速化のための機能も充実していたりします。VPSより割高めかも。

GoogleのGCPとかAmazonのAWSとかが有名です。国内だとさくらのクラウドGMOクラウドなどなど。会社によっては無料枠もあったりします。Misskeyの登録人数多めのサーバーはGCP使ってたりするのを見かけるかも。

自宅サーバー

家の中のPC(サーバー)に外部からアクセスさせます。機器のメンテナンス、OSやサーバーソフトウェアのインストール&メンテナンス、セキュリティ、ルータ含めネットワーク構成などある程度の知識が必要です。その分好きに組めますし、他より安価に済ませられたりします。

自分のIPをインターネット上に知らせる必要があるので、固定IPを契約するか、DDNSサービス(自分の動的IPを定期的にDNSサーバーに知らせてIP情報を更新して貰うサービス)を使用して公開します。

インターネット接続の基本の話

インターネットについて

インターネットでサイトを見るとはどういうことか?のかなりざっくりな整理です。プロトコル関連などは省略しています。

グローバルIP

インターネットに接続するとき、私たちの回線にはグローバルIPアドレスという「123.456.789.12」みたいなアドレスが契約しているプロバイダから割り振られています。これはインターネット上の番地みたいなもので、「東京都新宿区新宿1-2-3」みたいなものを表しています。

プライベートIP

さらに複数デバイスを1つの回線で使用するので、家の中ではそれぞれの機器(PCやスマホなど)にはルーターからプライベートIPアドレスが割り振られています。「192.168.0.*」とかみたいな形式が多いです。

これは上記の東京都~の住所に対してマンションの部屋番号「101」「205」みたいなもので、そのマンションの中では有効でも、このアドレスだけが分かった所でグローバルIPこと住所がわからないと特定までができません。ただしそのマンション内では有効なので、色々と便利に使用できます。

ホスト名とDNSサーバー

自分のグローバルIPアドレスを使って「sample.com」というサイトを見たいとき、まずDNSサーバーというサーバーに接続して問い合わせすることで「sample.com」がホスト名として設定されている接続先のグローバルIP「23.456.789.10」がわかります。

そしてwebサイトが見たい場合は一般的には相手がwebサーバーとして公開している80番ポート(https接続なら443ポート)にアクセスするので「23.456.789.10:80(または443)」に接続し、相手はこちらの要求に基づいてサイトのデータを返してくれます。(かなりざっくりです)

固定IPと動的IP

一昔前はプロバイダと契約するときに個人でもこのグローバルIPの特定の番号を占有して契約する固定IPか、接続する毎に空いているIPに変わる動的IPかが選べました。固定はアドレスを占有するので元々お高めでしたが…。

しかし、この「3桁.3桁.3桁.3桁」のIPv4アドレスは遂に数が足りなくなってしまい、新規に割り振ることが出来なくなってしまいました。(;o;)オヨー

その為、各社は自分の使用できるIPv4アドレスからやりくりするしかなくなり、IPv4固定IP契約は基本法人のみなどになってしまい、個人で契約するのが難しくなってしまっています。

固定IPであれば自分の家の回線に割り当てられた「123.456.789.12」のようなアドレス=住所は不変なので、これを取得した独自ドメイン(estampie.workなど)に割り当ててDNSサーバーを設定すれば自宅サーバーをインターネット上に公開できます。

しかし、動的IPは接続する度に自分のIPが変わるので、公開にはDDNSサービスというものを利用したりします。これは自分のIPを定期的に送信して独自ドメイン(estampie.workなど)の接続先のIPを変更して貰うことができるサービスです。無料のMyDNSなどが便利です。

IPv6アドレスの誕生

IPv4アドレスは枯渇してしまったため、新たにIPv6という方式のIPアドレスが採用されました。「2001:0db8:1234:5678:90ab:cdef:0000:0000」みたいな形式をしています。たくさんIPアドレスが作れるのでハッピーです。

しかし、IPv6のアドレスからはIPv6対応サイトしか見ることができません。現在IPv6に対応している大手サイトはWikipediaやFacebookなど一部のみで、YahooやTwitter等は対応していないので、従来のIPv4アドレスも持っていないと不便なのです。また、IPv6対応サイトに変更するには手間もかかるのでなかなか移行が進みません。

IPv4 over IPv6

そういうわけでIPv4も使用できないと大抵のサイトが見られない為、最近の回線は「IPoE(IPv6)方式だけどIPv4にも接続できる」ようになっています。変換にはNAT64/DNS64とか色々方法があるのですが、現在広く一般に利用されているのはIPv4 over IPv6というものです。

IPv4パケットをIPv6パケット内にカプセル化して転送する…というもので、フレッツ光クロスで使用できるIPoE通信(IPv6のみが使える)のままIPv4のサイトを見ることが出来ます。

プロバイダによりますが採用されている方法は大きく二通りあり、その代表的な物が以下になります。

DS-Lite方式

ユーザーにそれぞれプライベートIPアドレスを与えているイメージです。使用しているユーザーの匿名性は高くなります。故にポート開放が行えません。

Map-E方式

IPv4アドレスを複数人でシェアするイメージです。http通信用の80番ポートやhttps通信用の443番ポートなどウェルノウンポートは塞がれていますが、割り当てられた範囲内のポートの開放が可能です。

Map-E方式ではポート開放が出来る

ラッキーだったのはOCNバーチャルコネクトがMap-E方式だったことです。使用できるポートはプロバイダが割り振る形にはなりますが、仮に41000-42000ポートが割り当てられていたら「123.456.789.12(ここは与えられた動的IP):41000~42000」で外部から自宅のルーターに接続できるのです。

さらに家庭で使用しているルーターにポートフォワーディング(ポートマッピング)機能があります。(うちではNECのWX11000T12を使用しています)

上記のような設定を登録すると、「http://123.456.789.12(自宅に割り当てられたグローバルIP):41022/」にアクセスされたら自宅のルーターを介して宅内のプライベートIPアドレス「192.168.10.11:80」へと転送することができるのです。

(既に宅内に繋がっているので変換先を80/443ポートで受けなくても良いですが、Nginxなどの設定が楽)

この場合宅内の192.168.10.11の割り振られた機器でhttpサーバー(80番ポート)が開放されていればそのwebサーバーへインターネット上の他のPCからアクセスできるようになります。(グローバルIPアドレスと違い家の中のプライベートIPアドレスを固定するのは簡単なので、宅内のサーバーにするPCを192.168.10.11に固定しておきます)

しかしestampie.workという独自ドメインを取得しても、DDNSサービスではホスト名の解決(estampie.work→123.456.789.12)は出来てもポートを選んで送ることができません。

「123.456.789.12」というIPアドレスは他の契約者と共用しているのです。

このため上記の例で言うと自分のPCには「http://123.456.789.12:41022/」にアクセスして貰わないといけないわけです。ホスト名をDNSで割り当てても「http://estampie.work:41022/」でアクセスしないといけないので、ちょっと不格好すぎますし、ポートの違う別の人のIPにもestampie.workが仕向けられる感じがあってよろしくありません。

VPS→自宅サーバーの設定

VPSサーバーを借りてNginxでリバースプロキシを噛ませる

そこで、間にVPSサーバーを借りて挟むことでこれを解決します。

VPSサーバーはIPv4アドレスが与えられている契約が多いので(IPv6だけとかもあるので注意)、「estampie.work」の割り当て先のIPを自宅のIPではなく、VPSサーバーのIPアドレス「45.678.901.23(仮)」にしてDNS設定をします。

VPSサーバー「45.678.901.23」にアクセスされたら、その接続をNginxのリバースプロキシで「123.456.789.12:41022」に変えてあげるのです。

広く使用されているNginxは軽量なwebサーバーアプリケーションで、リバースプロキシとしても機能します。リバースプロキシというのは、プロキシサーバーが宅内からのアクセスの間に入って安全を守ったりするのの逆の動きで、宅外からのアクセスに対して負荷対策や高速化など色々処理を行います。

借りたVPSサーバーにセキュリティ設定をしたあと、Nginxをインストールして「自宅サーバーのIPアドレス:開放したポート番号」へ接続させる設定にします。

server {
    listen 80;
    server_name estampie.work;

    # Let's Encrypt の認証のための設定
    location ^~ /.well-known/ {
        root /var/www/html; 
    }

    # それ以外の場合はHTTPSにリダイレクト
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name estampie.work;

    ssl_certificate 証明書ファイル.pem;
    ssl_certificate_key 証明書鍵.pem;
    ssl_verify_client off;

    location / {
        proxy_pass https://自宅サーバーのIPアドレス:開放したポート番号/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

こんな感じでproxy_pass https://自宅サーバーのIPアドレス:開放したポート番号/;を指定してあげるとVPSサーバー経由で自宅サーバーが公開できます。

https通信のための証明書はこのVPSサーバーの方に設定します。VPS←→自宅サーバー間のhttps通信は自宅サーバー側に自己証明書があればOKです。自己証明書を使用するのでVPS→自宅サーバーのNginx設定にはssl_verify_client off;を付けておきます。

IPアドレスの更新

しかし、プロバイダから与えられるIPv4アドレスも動的IPなので、おそらく再接続などで自宅サーバーのIPアドレスは変わってしまいます。

これを解決するため特定のURLへhttps+BASIC認証でのアクセスが成功したらそのIPを記録し、上記のNginxの自宅サーバーのIPアドレス部分を新しいIPに変更するプログラムを組みます。(MyDNSさんの仕組みを参考にさせて頂きました)

具体的には「https://estampie.work/myapp/」(実際には違うよ)にアクセスするとBASIC認証のユーザー名とパスワードの入力画面が出るようにして、この認証に成功したPCのグローバルIPがセットされます。

自宅サーバーの方で自動で定期的に行うのでこんな感じのcronジョブを設定する感じになります。

# 5分ごとにVPSへIPを通知する
*/5 * * * * /usr/bin/curl -u ユーザー名:パスワード https://estampie.work/myapp/

ここは平文保存ですが宅内鯖は物理的になんとかしないと見れないので

ただしCDNなどでプロキシを使用しているとプロキシサーバーのIPがセットされてしまうので、CDNのプロキシからはこのアドレスを除外すること

PythonでFlaskアプリを作成し、Nginx+Gunicornで動かしました。コード系は折りたたみました↓ 実際にはもうちょっと改良してるかも

VPSサーバー側へFlaskをインストール

Flaskアプリの作成

sudo apt install python3-pip
pip install Flask
pip install Werkzeug

Werkzeugを使用するとソルト付きパスワードハッシュが簡単に作れます。メソッドやストレッチ回数はお好みで Pythonインタプリタで下記を実行

from werkzeug.security import generate_password_hash
myhash = generate_password_hash('パスワード', method='sha512', salt_length=1000)
print(myhash)

/opt/flask/とかのディレクトリを作成してapp.pyを作成

BASIC認証に成功したアクセス元のIPを/opt/flask/client_ipに保存します

from flask import Flask, request, Response
from werkzeug.security import check_password_hash

app = Flask(__name__)
app.debug = True

# Basic認証の情報
users = {
    "ユーザー名": "作成したパスワードハッシュ"
}

@app.route('/myapp/')
def get_ip():
    auth = request.authorization
    if not auth or not auth.username or not auth.password:
        return Response(status=401, headers={"WWW-Authenticate": "Basic realm='Login Required'"})

    if users.get(auth.username) and check_password_hash(users.get(auth.username), auth.password):
        client_ip = request.headers.get("X-Real-IP", request.remote_addr)
        with open("/opt/flask/client_ip", "w") as f:
            f.write(client_ip)
        return "IP saved.", 200

    return "Unauthorized", 401

if __name__ == "__main__":
    app.run(host="0.0.0.0",  ssl_context=('証明書.pem', '証明書鍵.pem'))

client_ip = request.headers.get("X-Real-IP", request.remote_addr)で127.0.0.1とかでなく接続元のIPが取れます。

Gunicornの設定

Flaskを本番環境でそのまま使用するのはあんまり良くないのでUSGI対応のHTTPサーバーのGunicornをインストールします。下記ではVPS内ローカルの127.0.0.1:8000にアクセスすることでapp.pyを実行できます

pip install gunicorn

Gunicornをサービスとして永続化させます。

/etc/systemd/system/gunicorn.serviceを作成して編集

[Unit]
Description=gunicorn daemon for Flask app
After=network.target

[Service]
User=Linuxのユーザー名
Group=www-data
WorkingDirectory=/opt/flask
# サービスのパスを確認しておく
ExecStart=/usr/local/bin/gunicorn app:app -b 127.0.0.1:8000

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl status gunicorn
# 成功したら永続化
sudo systemctl enable gunicorn

VPS側のNginxの該当のブロックに下記を追加

    # https://estampie.work/myapp/でapp.py実行
    location /myapp/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

proxy_set_header X-Real-IP $remote_addr;で正しい接続元IPを取得するので必須。

IP自動更新スクリプト

同フォルダにipupdate.shを作成しました。下記では/opt/flask/defaultに置いたNginx設定ファイルのIP部分を/opt/flask/client_ipに保存されたIPへと書き換えて、/etc/nginx/sites-available/defaultに上書きするスクリプトです。

/etc/nginx/sites-available/defaultを編集しても上書きされてしまうようになるので後で設定を変更したいときは注意。対象のブロックだけ別ファイルにするのが安全といえばそれはそう

/opt/flask/client_ipが更新されていないかチェックし、更新されていたら設定ファイルの更新とNginxの再起動をかけます。

#!/bin/bash

TEMPLATE_FILE="/opt/flask/default"
CLIENT_IP_FILE="/opt/flask/client_ip"
NGINX_CONFIG="/etc/nginx/sites-available/default"

# 現在の設定ファイルからproxy_passのIPを取得
CURRENT_IP=$(grep -oP '(?<=https://)[^:]+(?=:41022/;)' $NGINX_CONFIG)

# 新しいIPを取得
NEW_IP=$(cat $CLIENT_IP_FILE)

# 現在のIPと新しいIPが異なる場合のみ更新とNginx再起動を行う
if [ "$CURRENT_IP" != "$NEW_IP" ]; then
    # テンプレートから新しい設定ファイルを生成
    sed "s/123.456.789.12/$NEW_IP/g" $TEMPLATE_FILE > $TEMPLATE_FILE.tmp

    # 新しい設定ファイルを適用
    mv $TEMPLATE_FILE.tmp $NGINX_CONFIG

    # Nginx再起動
    systemctl reload nginx
fi

cronジョブで5分ごとにipupdate.shを実行します。

# IP更新
*/5 * * * * /opt/flask/ipupdate.sh >> /opt/flask/update_ip.log 2>&1

実際に使用してみて

実際にはCloudflareのCDNを利用しているので外部→CDN→VPS→自宅サーバーとなっています。CloudflareのAレコードの接続先IPv4アドレスをVPSサーバーのIPアドレスに設定すればOKです。

セキュリティ面

VPSと宅内サーバーのufw(ファイヤーウォール)のログをそれぞれ確認してみたところ、数日公開している分には宅内サーバー側への不正なSSHアクセスは検出されませんでした。VPSの方でufwやfail2banで色々弾けているので、かなり運用としては良い気がします。

自宅のIPを直で当てられるケースがありますが、この場合はSSHポートを変更しているのとポートスキャンが来たらブロックするようにfail2ban(試行しにきたIPをBAN)とPAM(試行回数でアカウント自体を一定時間ロック)で設定しているのでなんとかなりそうです。というかそもそも自宅鯖はアクセス元をローカル(192.168.10.*)からのみに絞ってましたね…

余計なパケットが自宅のルーターに届かないという面でもかなり良いと思います。

VPSと自宅サーバーでメンテナンスするものが増えるのでそこが面倒かも。

料金面

VPS側のプロセスのログを取って確認してみたところ、だいたいメモリ使用量は多くても300MBいかない程度でした。メモリ512MBのVPSサーバーでも充分かもしれません。とりあえずWebArenaVPSのメモリ1GB 473円/月プランが丁度良さそうです。

独自ドメインは使用料1,089円/年&Whois情報公開代行が1,078円/年で、180円/月ぐらい。

電気代の方はベアボーンのTDPが10Wなので、重い処理を行うサーバーでもないので重い処理入り続けて稼働した所で300円/月も行かないかな…とも。

Celron4コアですがメモリ16GB(増やせば32GB)使えてこの料金ならかなりコスパ良い感じかもです。ベアボーンの購入価格?そこは趣味なのでェ…ちなみに購入価格を割ると5年使った場合583円/月で普通にお安い 鯖止めてもPCとして使えるし長く使おうね

おわりに

IPv4アドレスの枯渇が原因でプロバイダがこういう仕様になっているのに、結局VPSサーバーのIPv4アドレスを使っているのでなんか本末転倒な感じはしますね…はやく世の中でIPv6が広まると良いですね…いつになるのやら…

Gunicorn,Flask