Blazor ServerをAWS Lightsail(Bitnami LAMP/Debian 12)へデプロイする完全手順|.NET 9・Apacheリバースプロキシ・WebSocket対応

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 を活用。
RIDlinux-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-*.logvhost ごとに分離しておくとトラブルシュートが容易。
アプリ更新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)
WebSocketWebSocket モジュールmod_proxy_wstunnel で中継
URL 再書きURL Rewritemod_rewrite
Forwarded Headers不要(終端直受け)必要(X-Forwarded-* を解釈)

確認フロー(デプロイ後)

  1. systemctl status myblazor が active (running)。
  2. curl -I https://example.com/ が 200 OK。
  3. ブラウザでアプリ表示、Network タブで /_blazor が 101 Switching Protocols(WebSocket)。
  4. 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 などのディレクティブに注意してください。

コピペ用:最小構成まとめ

フェーズコマンド/ファイル
Publishdotnet 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 HeadersProgram.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 コンポーネントも追加作業なしで動作します。あとはログ監視と更新手順を整えれば、本番運用に耐える堅牢な構成になります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次