🪁
Cloudflareを使って自宅Webサーバー(axum)をhttpsで無料公開したメモ
色々調べている時にCloudflareを調べていると、どうも自宅サーバーをWebサーバーとして無料で公開できるらしいことを知りました(ドメイン登録は有料)。これを使うとAWSのLightsailや他のVPS,レンタルサーバーとは比較にならない程の低ランニングコストで公開できることになります。それを実現しつつhttpsで公開する際につまづいたところも含めメモとして残しておきます。Webサーバーはaxumで自作しています。Cloudflareの画面構成が変わるため、スクショはありません。
(2024/09/25 追記)
比較的見ている人がいるようなのでクライアントIPのロギングについて追記しました。
前提
- まだドメインを登録していない
- Cloudflareでドメインを登録する
- 自宅サーバーにDockerインストール済み
- Docker無しでも実現は可能
- Webサーバーはaxumで構成する
- 静的ウェブサイトホスティング
手順
概要
- 任意のドメインを登録する
- 自宅サーバーをCloudflareと繋ぐ (Cloudflare Tunnel)
- 暗号化設定とサーバー証明書取得
- サーバー証明書を使用したWebサーバー構築
- Cloudflare Tunnelの設定変更
ドメイン登録
- "cloudflare ドメイン登録" 等で検索してCloudflareのドメイン登録にアクセス
- ドメインを検索・取得するためにCloudflareにアカウントを登録
- 登録したいドメインを検索して自分の情報とともに登録する
- 公式ドキュメントを参照可能
その他
- WHOIS情報の隠蔽はどうなるか (WHOIS redaction)
- Cloudflareで登録すると特別な手順無しに自動で隠蔽される
- ドメインの移管も可能だが、手順が異なる
- 公式ドキュメントを参照可能
- サイトの追加等の追加的な作業が必要な気がする
自宅サーバーを繋ぐ
Cloudflare Tunnel
- Cloudflare Zero Trustに移動する
- 初めての場合、Team domain名の登録が必要 (後から変更可能)
- この辺でプラン選択が出た気がするが、Freeを選択
-
公式ドキュメントを見ながら登録を進める
- 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的に最悪な気がする
- 設定は全て後から変更可能
暗号化設定と証明書取得
- 暗号化する経路を変更する
- 変更イメージ
- 変更前: クライアント --🔒-- Cloudflare --🔓-- 自宅サーバー
- 変更後: クライアント --🔒-- Cloudflare --🔒-- 自宅サーバー
-
公式ドキュメント
-
Full (strict)
を選択する
-
- 変更イメージ
- オリジン証明書作成画面を表示する
- 公式ドキュメント
- RSA,PEMファイル形式で
Origin Certificate
とPrivate Key
を生成 - ホストはデフォルトのまま, 有効期限は任意
-
Origin Certificate
とPrivate Key
をローカルにコピーしておく- 画面を閉じると
Private Key
は二度と表示できない
- 画面を閉じると
- 自宅サーバーにPEMファイルとして保存する
- Webサーバーソフトから参照しやすい場所に置く
-
Private Key
はchmod 600
しておく
その他
- PEMファイルの内容 (イメージ)
-----BEGIN CERTIFICATE-----
MIIEojCCA4qgAwIBAgIUNf6rnWPqXRTBco7aD5aXLkP530YwDQYJKoZIhvcNAQEL
...
L3CJaoheJsG5pOX93Qk9U40gsQYNxBBtYY+EFwzsFhAbNCRxFN4=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
BQAwgYsxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTQw
...
6NYUSl+72VIZfb7wPXx7g+PFeNuVq3UEK6z+Ymvv2+EM5ay37XLfmjT7MN0=
-----END CERTIFICATE-----
- Cloudflareのルート証明書が必要な場合は以下から取得する
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();
}
その他
Tunnel設定変更
自宅サーバーのhostsファイルに追記する場合
-
/etc/hosts
に127.0.0.1 mydomain.com
を追記する - Cloudflare Tunnelを以下のように設定する
- Public Hostname Page
- Public hostname:
www
.mydomain.com
- Service:
HTTPS
://mydomain.com
- Public hostname:
- Public Hostname Page
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:
- (TLS)
- Public hostname:
- Public Hostname Page
- No TLS Verify: ON の理由
- 自宅サーバー内部でTunnelとWebサーバー間でドメイン名を使用していないため
- SSL証明書のSANsに127.0.0.1が存在しないため
No TLS Verify: ON
が必須- SANs: Subject Alternate Names
- SSL証明書のSANsに127.0.0.1が存在しないため
- 自宅サーバー内部でTunnelとWebサーバー間でドメイン名を使用していないため
その他
- うまくいかない場合の調査方法
- デバッグでWebサーバーのコードが動作するか確認
- デバッグ用に簡単に証明書を作成する方法
- 自宅サーバーで動作させ、サーバー上で自分へのcurlで応答があるか確認
- ブラウザで応答があるか確認
- TunnelをDockerで実現している場合、以下でエラーログを確認する
docker logs [tunnel container id]
- TunnelをDockerで実現している場合、以下でエラーログを確認する
- デバッグでWebサーバーのコードが動作するか確認
クライアントIPについて
- Webサーバーで通常のログを取得すると全てのアクセス元IPが127.0.0.1になる
- Cloudflare Tunnelからのアクセスになるため
- この回避はできないが、取得できるようにCloudflareで処理がなされている
- リクエストヘッダの
CF-Connecting-IP
に元々のクライアントIPが格納される - そのためログにこの値をクライアントIPとして残せば良い
- 過去のアクセスについてクライアントIPを取得する手段は無さそう
- リクエストヘッダの
雑記
最後のTunnel設定でブラウザからhttpsでアクセスできずハマりました。axumも詳しくないのでそもそもどこでつまづいているのか地道に検証するはめに。原因は後から考えてみれば当たり前の原因でした。No TLS Verify
の方法はセキュリティ的にちょっと良くないと思うので、hostsファイルの方法の方がいいと思います。
Discussion