🪁

Cloudflareを使って自宅Webサーバー(axum)をhttpsで無料公開したメモ

2024/09/19に公開


色々調べている時にCloudflareを調べていると、どうも自宅サーバーをWebサーバーとして無料で公開できるらしいことを知りました(ドメイン登録は有料)。これを使うとAWSのLightsailや他のVPS,レンタルサーバーとは比較にならない程の低ランニングコストで公開できることになります。それを実現しつつhttpsで公開する際につまづいたところも含めメモとして残しておきます。Webサーバーはaxumで自作しています。Cloudflareの画面構成が変わるため、スクショはありません。

(2024/09/25 追記)
比較的見ている人がいるようなのでクライアントIPのロギングについて追記しました。

前提

  • まだドメインを登録していない
    • Cloudflareでドメインを登録する
  • 自宅サーバーにDockerインストール済み
    • Docker無しでも実現は可能
  • Webサーバーはaxumで構成する
    • 静的ウェブサイトホスティング

手順

概要

  1. 任意のドメインを登録する
  2. 自宅サーバーをCloudflareと繋ぐ (Cloudflare Tunnel)
  3. 暗号化設定とサーバー証明書取得
  4. サーバー証明書を使用したWebサーバー構築
  5. Cloudflare Tunnelの設定変更

ドメイン登録

  1. "cloudflare ドメイン登録" 等で検索してCloudflareのドメイン登録にアクセス
  2. ドメインを検索・取得するためにCloudflareにアカウントを登録
  3. 登録したいドメインを検索して自分の情報とともに登録する

その他

  • WHOIS情報の隠蔽はどうなるか (WHOIS redaction)
    • Cloudflareで登録すると特別な手順無しに自動で隠蔽される
  • ドメインの移管も可能だが、手順が異なる

自宅サーバーを繋ぐ

Cloudflare Tunnel

  1. Cloudflare Zero Trustに移動する
    • 初めての場合、Team domain名の登録が必要 (後から変更可能)
    • この辺でプラン選択が出た気がするが、Freeを選択
  2. 公式ドキュメントを見ながら登録を進める
    • Dockerを使用する場合は画面に提示されるコマンドよりも以下コマンドの方がいい気がする
    # To match host's network & run background
    docker run -d --network=host cloudflare/cloudflared:latest tunnel --no-autoupdate run --token [YOUR_TOKEN]
    

その他

  • 自宅サーバーでServiceがまだ稼働していない時は適当に登録可能
    • ただしこれで放置するのはそのドメインのSEO的に最悪な気がする
  • 設定は全て後から変更可能

暗号化設定と証明書取得

  1. 暗号化する経路を変更する
    • 変更イメージ
      • 変更前: クライアント --🔒-- Cloudflare --🔓-- 自宅サーバー
      • 変更後: クライアント --🔒-- Cloudflare --🔒-- 自宅サーバー
    • 公式ドキュメント
      • Full (strict)を選択する
  2. オリジン証明書作成画面を表示する
    • 公式ドキュメント
    • RSA,PEMファイル形式でOrigin CertificatePrivate Keyを生成
    • ホストはデフォルトのまま, 有効期限は任意
  3. Origin CertificatePrivate Keyをローカルにコピーしておく
    • 画面を閉じるとPrivate Keyは二度と表示できない
  4. 自宅サーバーにPEMファイルとして保存する
    • Webサーバーソフトから参照しやすい場所に置く
    • Private Keychmod 600しておく

その他

  • PEMファイルの内容 (イメージ)
-----BEGIN CERTIFICATE-----
MIIEojCCA4qgAwIBAgIUNf6rnWPqXRTBco7aD5aXLkP530YwDQYJKoZIhvcNAQEL
...
L3CJaoheJsG5pOX93Qk9U40gsQYNxBBtYY+EFwzsFhAbNCRxFN4=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
BQAwgYsxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTQw
...
6NYUSl+72VIZfb7wPXx7g+PFeNuVq3UEK6z+Ymvv2+EM5ay37XLfmjT7MN0=
-----END CERTIFICATE-----

Webサーバー構築

コード

Cargo.toml
[dependencies]
axum = { version = "0.7.5", features = ["http2"] }
axum-server = { version = "0.7.1", features = ["tls-rustls"] }
tokio = { version = "1.40.0", features = ["full"] }
tower-http = { version ="0.5.2", features = ["fs"] }
main.rs
use axum::Router;
use tower_http::services::ServeDir;
use axum_server::tls_rustls::RustlsConfig;
use std::{net::SocketAddr, path::PathBuf};

#[tokio::main]
async fn main() {
    // configure certificate and private key used by https
    let config = RustlsConfig::from_pem_file(
        PathBuf::from("/path/to/cert.pem"),
        PathBuf::from("/path/to/key.pem")
    )
    .await
    .unwrap();

    let serve_dir = ServeDir::new("./static");
    let app = Router::new().nest_service("/", serve_dir);
    // run https server
    let addr = SocketAddr::from(([127, 0, 0, 1], 443));
    axum_server::bind_rustls(addr, config)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

その他

  • axumの他にaxum-serverクレートを追加する必要がある

Tunnel設定変更

自宅サーバーのhostsファイルに追記する場合

  • /etc/hosts127.0.0.1 mydomain.comを追記する
  • Cloudflare Tunnelを以下のように設定する
    • Public Hostname Page
      • Public hostname: www.mydomain.com
      • Service: HTTPS :// mydomain.com

No TLS Verifyを使用する場合

  • Cloudflare Tunnelを以下のように設定する
    • Public Hostname Page
      • Public hostname: www.mydomain.com
      • Service: HTTPS :// 127.0.0.1
      • (Additional application settings)
        • (TLS)
          • No TLS Verify: ON
  • No TLS Verify: ON の理由
    • 自宅サーバー内部でTunnelとWebサーバー間でドメイン名を使用していないため
      • SSL証明書のSANsに127.0.0.1が存在しないためNo TLS Verify: ONが必須
        • SANs: Subject Alternate Names

その他

  • うまくいかない場合の調査方法
    • デバッグでWebサーバーのコードが動作するか確認
    • 自宅サーバーで動作させ、サーバー上で自分へのcurlで応答があるか確認
    • ブラウザで応答があるか確認
      • TunnelをDockerで実現している場合、以下でエラーログを確認する
        • docker logs [tunnel container id]

クライアントIPについて

  • Webサーバーで通常のログを取得すると全てのアクセス元IPが127.0.0.1になる
    • Cloudflare Tunnelからのアクセスになるため
  • この回避はできないが、取得できるようにCloudflareで処理がなされている
    • リクエストヘッダのCF-Connecting-IPに元々のクライアントIPが格納される
    • そのためログにこの値をクライアントIPとして残せば良い
    • 過去のアクセスについてクライアントIPを取得する手段は無さそう

雑記

最後のTunnel設定でブラウザからhttpsでアクセスできずハマりました。axumも詳しくないのでそもそもどこでつまづいているのか地道に検証するはめに。原因は後から考えてみれば当たり前の原因でした。No TLS Verifyの方法はセキュリティ的にちょっと良くないと思うので、hostsファイルの方法の方がいいと思います。

参考文献

Discussion