Blazor Server を Mac+VS Code で開発し、最終的に AWS Lightsail の Bitnami LAMP(Debian 12)へ安全かつ再現性高く載せるための手順を、IIS からの移行ポイントや WebSocket・SSL・常駐化(systemd)までまとめて解説します。.NET 9 環境が既に入っている前提で、フレームワーク依存デプロイ(FDD)を軸に、DevExpress Blazor Controls 利用時の注意点や、サブフォルダ配信・ベースパス設定まで網羅します。
想定環境とゴール
| 項目 | 内容 |
|---|---|
| アプリ種別 | Blazor Server(.NET 9/DevExpress Blazor Controls) |
| 開発環境 | macOS + VS Code |
| 配置先 | AWS Lightsail(Bitnami LAMP/Debian 12) |
| Webサーバー | Apache 2.4(Bitnami バンドル) |
| TLS終端 | Apache :443(リバースプロキシで Kestrel :5000 に中継) |
| 常駐化 | systemd サービス(自動起動・自動復旧) |
| 目的 | HTTPS + WebSocket(SignalR)対応で本番運用/80→443 リダイレクト |
結論だけ先に(質問へのショートアンサー)
- Linux 向け publish オプション:サーバーに .NET 9 ランタイムがある前提で FDD。例:
dotnet publish -c Release -r linux-x64 --self-contained false - 配置場所:Apache ドキュメントルートと分離して
/opt/myapps/MyBlazorAppのような専用ディレクトリを推奨(混在を避ける)。 - Apache の役割:TLS 終端+リバースプロキシ+WebSocket 中継。
mod_proxy/mod_proxy_http/mod_proxy_wstunnel/mod_headersを有効化し、/_blazorをws://で中継。
SSL は Apache に設定し、アプリ側はhttp://127.0.0.1:5000を待受。 - 常駐化(IIS 代替):
systemdサービス化で再起動時の自動起動/クラッシュ時の自動復旧。 - DevExpress/FW:DevExpress の静的アセットは publish に含まれる。Lightsail の外部ファイアウォールは 80/443 のみ開放(5000 は閉じる)。
全体アーキテクチャと通信経路
本番構成は次の一方向リバースプロキシです。Apache が HTTPS を終端し、内部の Kestrel(.NET アプリ)にフォワードします。
Client (Browser, HTTPS)
│
▼
Apache 2.4 :443 ──(Reverse Proxy / WebSocket)──> Kestrel :5000 ──> Blazor Server (.NET 9)
- TLS終端
- HTTP→HTTPSリダイレクト
- /_blazor を ws:// で中継
- X-Forwarded-* ヘッダー付与
事前チェックリスト
- Lightsail インスタンスに SSH 接続できる(
bitnamiユーザー)。 - .NET 9 ランタイム/SDK が導入済み(
dotnet --infoで確認)。 - 独自ドメインは Route 53 で Lightsail の Public IP を指している(A / ALIAS)。
- SSL 証明書は Apache に設定可能なファイル形式で入手済み(
.crtと.key)。 - Lightsail のファイアウォールで 80/TCP と 443/TCP を開放、その他は閉じる。
# .NET ランタイム確認(サーバー)
dotnet --list-runtimes
# Apache 稼働確認(Bitnami)
sudo /opt/bitnami/ctlscript.sh status
# OS 情報
cat /etc/debian_version
フェーズ1:Linux 向けに Publish(FDD で OK)
サーバーに .NET 9 があるため、フレームワーク依存デプロイ(FDD) でシンプルに配布できます。CPU アーキテクチャに合わせて RID を選びます(Lightsail x86_64 なら linux-x64)。
# macOS / 開発端末
dotnet publish -c Release -r linux-x64 --self-contained false
# 発行物はここ(例)
bin/Release/net9.0/linux-x64/publish/
| 設定 | 推奨値 | 理由 |
|---|---|---|
| デプロイ形態 | FDD(–self-contained false) | 配布サイズが小さく更新容易。サーバーの .NET を活用。 |
| RID | linux-x64 / linux-arm64 | インスタンスの CPU に合わせる。 |
| ReadyToRun | 任意(本番で検討可) | 起動高速化だがビルド時間・サイズ増。まずはデフォルトで十分。 |
| Trimming | 無効(既定) | Blazor Server はトリミング非推奨。ランタイム解析が多い。 |
Self-contained(SCD)で配る選択もありますが、サイズ増・パッチ適用の運用差異が出るため、まずは FDD を推奨します。
フェーズ2:サーバーへ転送・配置(ディレクトリ設計)
Apache のドキュメントルート(/opt/bitnami/apache/htdocs)とは分離し、アプリ専用ディレクトリを切ると保守が楽です。
# 例:アプリのホームを作成(サーバー)
sudo mkdir -p /opt/myapps/MyBlazorApp
sudo chown -R bitnami:daemon /opt/myapps
sudo chmod -R 775 /opt/myapps
# macOS から rsync で配布物を転送(高速・差分)
rsync -avz --delete ./bin/Release/net9.0/linux-x64/publish/
bitnami@:/opt/myapps/MyBlazorApp/
もちろん SCP/SFTP でも構いません。権限は bitnami:daemon を基準に 775/664 程度に揃えると、Apache からも読みやすく安全です。
フェーズ3:systemd サービス化(自動起動・自動復旧)
IIS の代わりに systemd で常駐化します。ネットワーク後に起動し、落ちたら自動再起動する設定を入れます。
# サービスファイル作成(サーバー)
sudo tee /etc/systemd/system/myblazor.service > /dev/null <<'EOF'
[Unit]
Description=My Blazor Server App (Kestrel)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=bitnami
Group=daemon
WorkingDirectory=/opt/myapps/MyBlazorApp
ExecStart=/usr/bin/dotnet /opt/myapps/MyBlazorApp/MyBlazorApp.dll --urls=[http://127.0.0.1:5000](http://127.0.0.1:5000)
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
# セキュリティ・運用
Restart=always
RestartSec=5
SyslogIdentifier=myblazor
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
UMask=0027
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
EOF
# 有効化と起動
sudo systemctl daemon-reload
sudo systemctl enable --now myblazor
# 状態とログ
systemctl status myblazor --no-pager
journalctl -u myblazor -f
ポイント:待受は 127.0.0.1:5000 に限定し、外部から直接触れないようにします。公開は Apache からのプロキシ経由のみです。
フェーズ4:Apache のリバースプロキシ(HTTPS/WebSocket)
4-1. 必要モジュールの確認
Bitnami の Apache は多くのモジュールが事前に組み込まれています。以下のモジュールがロードされていることを確認します。
proxy_module,proxy_http_module,proxy_wstunnel_module,headers_module,ssl_module
有効化は /opt/bitnami/apache/conf/httpd.conf の LoadModule 行を確認・追記する方法が一般的です(Bitnami 環境では a2enmod は使いません)。編集後は Apache 再起動で反映します。
4-2. 仮想ホスト(:443)の作成
Bitnami 既定の SSL 仮想ホストに追記するか、専用 vhost を作成します。ここでは分離のため vhost ファイルを新規に切る例を示します。
# vhost ディレクトリ(なければ作成)
sudo mkdir -p /opt/bitnami/apache/conf/vhosts
# vhost ファイル作成
sudo tee /opt/bitnami/apache/conf/vhosts/myblazor-https-vhost.conf > /dev/null <<'EOF'
ServerName example.com
# TLS 終端(Bitnami 既定の証明書パス例。実際のパスに合わせて変更)
SSLEngine on
SSLCertificateFile "/opt/bitnami/apache/conf/bitnami/certs/server.crt"
SSLCertificateKeyFile "/opt/bitnami/apache/conf/bitnami/certs/server.key"
# プロキシ基本設定
ProxyRequests Off
ProxyPreserveHost On
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
# WebSocket(/_blazor)を ws:// に中継
ProxyPass "/_blazor" "ws://127.0.0.1:5000/_blazor"
ProxyPassReverse "/_blazor" "ws://127.0.0.1:5000/_blazor"
# それ以外は http:// に中継
ProxyPass "/" "[http://127.0.0.1:5000/](http://127.0.0.1:5000/)"
ProxyPassReverse "/" "[http://127.0.0.1:5000/](http://127.0.0.1:5000/)"
# タイムアウト調整(長時間接続に配慮)
ProxyTimeout 600
Timeout 600
# 最低限のアクセス制御
Require all granted
ErrorLog "/opt/bitnami/apache/logs/myblazor-error.log"
CustomLog "/opt/bitnami/apache/logs/myblazor-access.log" combined
EOF
vhost の Include が有効でない場合は、/opt/bitnami/apache/conf/bitnami/bitnami.conf 等に以下を追加して vhost を読み込ませます。
IncludeOptional "/opt/bitnami/apache/conf/vhosts/*.conf"
4-3. HTTP → HTTPS リダイレクト(任意)
80番へ来たリクエストを 443 番へリダイレクトします。Bitnami 既定の 80 番 VirtualHost に次を追加します。
<VirtualHost *:80>
ServerName example.com
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
</VirtualHost>
4-4. 構文チェックと再起動
sudo /opt/bitnami/apache/bin/apachectl -t
sudo /opt/bitnami/ctlscript.sh restart apache
sudo tail -f /opt/bitnami/apache/logs/myblazor-error.log
チェックポイント:初回アクセスで /_blazor/negotiate が 200、wss://example.com/_blazor へのアップグレードが成功していること(ブラウザ開発者ツールの Network タブで確認)。
フェーズ5:アプリ側(Program.cs)のプロキシ対応
Apache で TLS を終端すると、アプリは http で受けているため、リダイレクトや生成 URL に混乱が生じます。Forwarded Headers を有効にし、必要ならパスベース(サブフォルダ運用)も設定します。
// Program.cs (.NET 9)
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
// X-Forwarded-* を信頼(プロキシ直後の1段のみ)
builder.Services.Configure(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownProxies.Clear(); // 127.0.0.1 のみ経由なら不要でも可
});
var app = builder.Build();
app.UseForwardedHeaders();
// (サブフォルダ配信例:/app で公開したい場合)
// app.UsePathBase("/app");
// app.MapWhen(ctx => true, branch => { /* 既存のルーティング */ });
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
POINT:サブフォルダで公開する場合は Apache 側も ProxyPass "/app" "http://127.0.0.1:5000/app" のように合わせ、アプリでは UsePathBase("/app") を忘れないでください。
フェーズ6:DevExpress Blazor Controls の注意点
- 静的アセット:CSS/JS は
_content/DevExpress.Blazor/配下から提供されます。通常のdotnet publishに含まれるため、特別なコピーは不要です。 - WebSocket:Blazor Server は SignalR を使用。Apache の
/_blazor向けws://中継と、mod_proxy_wstunnelのロードが必須です。 - ロケール/カルチャ:カルチャ別リソースを使う場合はサーバーのカルチャ(
CultureInfo)やThread.CurrentThread.CurrentCultureを適切に初期化。 - ライセンス:DevExpress のライセンス設定(ビルド時埋め込み)を済ませる。サーバー側で追加の作業は基本不要。
フェーズ7:Lightsail/Debian のファイアウォール設計
- Lightsail ネットワーク(コンソール):80/TCP と 443/TCP を「許可」。5000/TCP は公開不要。
- OS 側:Bitnami イメージは UFW 無効が多いですが、導入している場合は
sudo ufw allow 80,443/tcpのみ開放。 - プロセスの待受:アプリは
127.0.0.1:5000のみ待受(--urls=http://127.0.0.1:5000)。
フェーズ8:運用・監視・更新
| 対象 | 場所/コマンド | メモ |
|---|---|---|
| アプリログ | journalctl -u myblazor -f | リアルタイムで追跡。永続化は journald のポリシーに依存。 |
| Apache ログ | /opt/bitnami/apache/logs/myblazor-*.log | vhost ごとに分離しておくとトラブルシュートが容易。 |
| アプリ更新 | rsync or scp → sudo systemctl restart myblazor | 再起動は瞬断が出るため、メンテ時間帯に。 |
| 証明書更新 | 証明書差し替え → Apache 再起動 | 自動更新ツール(lego / bncert 等)の利用も可。 |
| OS 再起動後 | systemctl is-enabled myblazor | 自動起動(enabled)を確認。 |
トラブルシュート(実践的な落とし穴と対処)
WebSocket が確立しない(Long Polling に落ちる)
mod_proxy_wstunnelがロードされていない:LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.soを確認。- Apache vhost に
/_blazorのws://中継がない:ProxyPass "/_blazor" "ws://127.0.0.1:5000/_blazor"を追加。 - ロードバランサ/WAF で WebSocket がブロック:Lightsail 単体なら通常不要だが、ネットワーク経路を確認。
HTTPS でループまたは http へリダイレクトされる
- アプリ側で
UseForwardedHeadersを忘れている:X-Forwarded-Protoを解釈させる。 - Apache 側で
RequestHeader set "X-Forwarded-Proto"を付けていない:追加する。
サブフォルダ配信で静的ファイルが 404
- アプリに
UsePathBase("/app")を設定していない。 - Apache の
ProxyPass "/app" "http://127.0.0.1:5000/app"が不足。
502 / 504(ゲートウェイエラー)
- アプリが停止:
systemctl status myblazorとjournalctlでクラッシュ確認。 - タイムアウト:Apache 側
ProxyTimeoutとTimeoutを延長。
アセット(_content/*)が読めない
- 権限問題:
chown -R bitnami:daemonと 664/775 を確認。 - Publish 漏れ:
dotnet publishの出力先にアセットが含まれているか確認。
サブフォルダ(/app)で公開したい場合の完全例
Apache 側
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile "/opt/bitnami/apache/conf/bitnami/certs/server.crt"
SSLCertificateKeyFile "/opt/bitnami/apache/conf/bitnami/certs/server.key"
ProxyRequests Off
ProxyPreserveHost On
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
# /app だけを Kestrel へ
ProxyPass "/app/_blazor" "ws://127.0.0.1:5000/app/_blazor"
ProxyPassReverse "/app/_blazor" "ws://127.0.0.1:5000/app/_blazor"
ProxyPass "/app/" "[http://127.0.0.1:5000/app/](http://127.0.0.1:5000/app/)"
ProxyPassReverse "/app/" "[http://127.0.0.1:5000/app/](http://127.0.0.1:5000/app/)"
ProxyTimeout 600
Timeout 600
アプリ側(Program.cs)
app.UsePathBase("/app");
app.Map("/app", branch =>
{
branch.MapBlazorHub();
branch.MapFallbackToPage("/_Host");
});
Blazor Server では _Host.cshtml が入り口になるため、Wasm で行うような index.html の <base> タグ編集は不要です(必要なのは UsePathBase とプロキシ設定)。
セキュリティ・運用のベストプラクティス
- 最小公開:外部公開は 80/443 のみ。アプリはローカルホストで待受。
- 権限分離:実行ユーザーは
bitnami(またはシステム専用ユーザー)に限定。rootでの常時実行は避ける。 - 定期更新:.NET ランタイムと OS のパッチ適用を計画。アプリ更新は rsync で差分配布。
- ログ保全:
journalctl --vacuum-time=14dなどでログ肥大化を抑制。 - ヘルスチェック:アプリに軽量な
/healthzエンドポイントを用意し、curl で監視。
IIS からの移行で「変わること/変わらないこと」
| 観点 | IIS(Windows) | Apache + Kestrel(Linux) |
|---|---|---|
| 常駐 | アプリプール | systemd サービス |
| TLS 終端 | IIS 証明書ストア | Apache の vhost(.crt/.key) |
| WebSocket | WebSocket モジュール | mod_proxy_wstunnel で中継 |
| URL 再書き | URL Rewrite | mod_rewrite |
| Forwarded Headers | 不要(終端直受け) | 必要(X-Forwarded-* を解釈) |
確認フロー(デプロイ後)
systemctl status myblazorが active (running)。curl -I https://example.com/が200 OK。- ブラウザでアプリ表示、Network タブで
/_blazorが 101 Switching Protocols(WebSocket)。 - Apache エラーログにハンドシェイクの失敗が出ていない。
参考:Self-Contained(SCD)で配りたい場合
サーバー側ランタイムに依存したくない場合は SCD を選べます。
dotnet publish -c Release -r linux-x64 --self-contained true /p:PublishSingleFile=true
ただしサイズ増・ビルド時間増・CVE 対応の運用が FDD と変わる点に留意してください。
よくある QA
Q. ファイル配置は htdocs 配下でも良い?
A. 技術的には可能ですが、PHP アプリや静的コンテンツと混在しやすく、権限やバージョン管理が複雑化するため、/opt/myapps/<AppName> のように分離を推奨します。
Q. Apache を使わず Kestrel 直で 443 を聴かせるのは?
A. 可能ですが、権限・証明書自動更新・多言語サイトや他アプリ共存の柔軟性で Apache(または Nginx)リバースプロキシ構成が一般的に有利です。
Q. DevExpress 固有の追加設定は?
A. 基本は不要です。SignalR(WebSocket)と静的アセットが正しく運ばれれば動作します。CSP を厳格化している場合は connect-src などのディレクティブに注意してください。
コピペ用:最小構成まとめ
| フェーズ | コマンド/ファイル |
|---|---|
| Publish | dotnet publish -c Release -r linux-x64 --self-contained false |
| 転送 | rsync -avz ./publish/ bitnami@HOST:/opt/myapps/MyBlazorApp/ |
| systemd | /etc/systemd/system/myblazor.service を作成 → systemctl enable --now myblazor |
| Apache vhost | /opt/bitnami/apache/conf/vhosts/myblazor-https-vhost.conf を作成 → 再起動 |
| Forwarded Headers | Program.cs に UseForwardedHeaders() |
| 疎通 | curl -I https://FQDN/、ブラウザで /_blazor が 101 |
まとめ
Lightsail(Bitnami LAMP/Debian 12)への Blazor Server デプロイは、(1) FDD で publish、(2) /opt/myapps に配置、(3) systemd で常駐、(4) Apache で TLS とリバプロ/WebSocket 中継、(5) Forwarded Headers と(必要なら)PathBase の調整、という 5 ステップを踏めば安定して稼働します。IIS 相当の常時稼働を Linux 上で実現しつつ、DevExpress コンポーネントも追加作業なしで動作します。あとはログ監視と更新手順を整えれば、本番運用に耐える堅牢な構成になります。

コメント