Angular フロントエンドと ASP.NET Core API、Azure SQL Database を組み合わせて Azure App Service にデプロイした途端、ローカルでは動いていたのにブラウザからの API 呼び出しがすべて HTTP 500 になる——そんな状況に悩む開発者は少なくありません。本記事では、特に Windows(IIS)版 App Service を前提に、500 エラーの原因をどこから切り分ければよいのか、実際に効果のあった設定例とあわせて詳しく解説します。
Angular+ASP.NET Core+Azure SQL を Azure に発行したときの典型シナリオ
まずは、この記事で想定している構成と症状を整理します。
- フロントエンド:Angular(ブラウザ SPA)
- API:ASP.NET Core Web API(.NET 6 / 7 / 8 など)
- インフラ:Azure App Service(Windows プラン, IIS 経由)、バックエンド DB は Azure SQL Database
- 認証:Microsoft Entra ID(旧 Azure AD)での認証/認可を利用する可能性あり
ローカル開発環境(IIS Express や dotnet run+ローカル SQL Server/Azure SQL 直結)では正常に動作していたものの、次のような症状が本番環境で発生します。
- ブラウザの開発者ツールから API を叩くと HTTP 500(Internal Server Error) が返ってくる
- CORS はいったん解消済みで、400 番台ではなく 500 番台として失敗している
- ときどき
500.30や500.31のような ANCM(ASP.NET Core Module)エラーが表示される - Angular 側のエラーメッセージには詳細が出ず、「サーバー内部エラー」とだけ表示される
この状況で重要なのは、「どこで 500 が発生しているのか」を切り分けることです。IIS/ANCM レベルで起動に失敗しているのか、ASP.NET Core アプリ内部で例外が発生しているのか、あるいは DB 接続や認証で例外になっているのかで、取るべきアプローチが大きく変わります。
どこで 500 が発生しているかを特定する:Azure App Service のログの見方
App Service(Windows)では、ポータルと Kudu(高度なツール)を使うことで、かなり詳細なログを取ることができます。まずはここを押さえておきましょう。
Azure ポータルから確認できる主なログの種類
| 場所 / 種別 | 確認方法 | 主な内容 | 500 調査でのポイント |
|---|---|---|---|
| ログ ストリーム | App Service メニュー「ログ ストリーム」 | ASP.NET Core のコンソール出力、未処理例外、ANCM エラーなど | 起動時例外やランタイム例外のスタックトレースが出るかを確認 |
| アプリケーション ログ | 構成 > 監視 > App Service ログ | ILogger などで出力したアプリケーションログ | 接続文字列や認証周りで失敗していないかを確認 |
| Web サーバー ログ | 同上(IIS ログ) | リクエストパス/ステータスコード/レスポンスサイズなど | どの URL で 500 が多発しているかを特定 |
| 失敗した要求トレース | Windows プランのみ有効化可能 | IIS レベルの詳細なトレース | IIS 構成やモジュールエラー(ANCM 含む)の詳細を掴む |
| Kudu の各種ログ | 高度なツール > Debug console | D:\home\LogFiles 以下の eventlog, stdout など | ANCM の副番号(500.30 など)や .NET Runtime エラーの詳細を確認 |
特に、500 エラーの画面に 「HTTP Error 500.30 – ANCM In-Process Start Failure」 のようなメッセージが出ている場合は、IIS/ANCM が Kestrel(ASP.NET Core プロセス)を起動できていないことを示します。その際は、Kudu の eventlog.xml や、stdout ログを開いてスタックトレースを確認しましょう。
ログを確認するときのコツ
- まずは「再現用の 1 リクエスト」を投げ、そのタイミングでログ ストリームを眺める
- 例外が出ていない場合は、アプリ側で
UseDeveloperExceptionPageや ILogger を増やして再デプロイする - アプリは正常だが 500.30/500.31 のような ANCM エラーが出る場合は、「ランタイム不一致」か「web.config の問題」を疑う
どのログにも何も出てこない場合、web.config が正しく配置されていない・IIS が web.config を読めていない 可能性もあります。次のセクションで詳しく見ていきます。
Azure App Service(Windows)では web.config がほぼ必須
「ASP.NET Core は appsettings.json で設定するし、web.config はいらないのでは?」と思われがちですが、Windows 版 Azure App Service では IIS がフロントに立つため、IIS 設定を記述した web.config が実質必須です。
web.config の役割
- IIS に「どのパスを ASP.NET Core アプリに転送するか」を教える
- どのモジュール(
AspNetCoreModuleV2)でハンドリングするかを指定する - アプリ実行ファイル(processPath)と引数(arguments)を指定する
ASP.NET Core アプリ自身は web.config を読むわけではありません。あくまで IIS と ANCM のための設定ファイルです。そのため、web.config は通常の ASP.NET MVC のような巨大なファイルである必要はなく、最低限の設定だけが書かれていれば問題ありません。
web.config の典型例
発行先の wwwroot に、以下のような web.config が生成されているか確認してください(内容は一例です)。
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*"
modules="AspNetCoreModuleV2"
resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet"
arguments="".\MyApi.dll""
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout"
hostingModel="inprocess" />
</system.webServer>
</configuration>
ここで重要なのは次のポイントです。
modules="AspNetCoreModuleV2"になっているかprocessPathとargumentsが、発行したファイル構成と一致しているか- 自己完結(self-contained)発行の場合は、
processPathがdotnetではなく実行ファイル名になっているか
もし web.config が存在しない、またはルートではなくサブフォルダに配置されていると、IIS は正しく ASP.NET Core アプリを起動できず、500(特に 500.19, 500.30 など) で落ちることがあります。ビルド/発行プロファイルで web.config を削除対象にしていないか も確認しておきましょう。
Azure SQL Database 接続でよくハマるポイント
Angular+ASP.NET Core+Azure SQL の構成では、実は DB 接続が原因で 500 になっているケースが非常に多いです。代表的なパターンと対処方法を整理します。
よくある原因と症状の対応表
| 原因 | 症状 | 対処 |
|---|---|---|
| SQL ファイアウォールで App Service の IP が許可されていない | 接続タイムアウト/SQL エラー(10060 など) | 「Azure サービスおよびリソースからのアクセスを許可」を有効化 |
| 接続文字列がローカル用のまま(localhost, (localdb) など) | ホスト名解決エラー/接続失敗 | 本番用のサーバー名(xxx.database.windows.net)に変更 |
| SQL ログイン/Entra ID ユーザーに DB 権限がない | ログインは成功するが、認可エラー(Cannot open database ... requested by the login) | データベース上でユーザー作成と db_datareader, db_datawriter などの権限付与 |
| Service Connector / Service Dependencies と appsettings.json が競合 | 意図しない接続先 DB に繋がり、スキーマ不一致で例外 | 接続設定の管理場所をどちらか一方に統一し、不要な依存設定を削除 |
SQL ファイアウォール設定のベストプラクティス
Azure SQL の「ネットワーク」設定には、クライアント IP の許可だけでなく、「Azure サービスおよびリソースからこのサーバーへのアクセスを許可する」というトグルがあります。これは内部的に 0.0.0.0 のファイアウォールルールとして扱われ、Azure 内の各種サービス(App Service など)からのアクセスを一括で許可します。
App Service の送信 IP を 1 つずつファイアウォールに登録することもできますが、スケールアウトやインフラ側の変更で IP が増減した場合に追従しきれません。まずは 0.0.0.0 ルールを有効化して動作確認し、その後必要に応じて制限を厳しくする、という段階的なアプローチが安全です。
Microsoft Entra ID(マネージド ID)を使ったパスワードレス接続
本番環境では、アプリケーション構成に SQL ユーザー名・パスワードをベタ書きするのは避けたいところです。そこで有効なのが、App Service のマネージド ID と ADO.NET の Entra 認証です。
- App Service の「ID」メニューで「システム割り当てマネージド ID」を有効化
- Azure SQL サーバーに対して、このマネージド ID を Entra ユーザーとして付与
- 対象データベースで T-SQL を実行してユーザー作成と権限付与
-- master ではなく対象 DB 上で実行
CREATE USER [<AppServiceName>] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [<AppServiceName>];
ALTER ROLE db_datawriter ADD MEMBER [<AppServiceName>];
接続文字列は次のようにシンプルになります。
Server=tcp:xxxx.database.windows.net,1433;
Database=YourDbName;
Authentication=Active Directory Default;
Encrypt=True;
TrustServerCertificate=False;
アプリ側で特別なトークン取得コードを書かなくても、実行しているコンテキスト(この場合は App Service のマネージド ID)が自動的に利用されます。これにより、ID・パスワードの管理が不要となり、セキュリティと運用性の両方が向上します。
接続文字列の「二重管理」を避ける
Visual Studio から Azure リソースを紐づける際に、Service Dependencies / Service Connector を使うと、自動的に接続設定が作成されます。一方で、従来どおり appsettings.json や App Service の「構成」で接続文字列を手動で設定していると、結果として 複数の接続設定が混在することになり、意図しない接続先に繋がってしまうことがあります。
次のように整理するのがおすすめです。
- 「Service Dependencies を使う」か「appsettings.json+構成だけで管理する」かを決めて、どちらか一方に統一する
- 使わない方の仕組みで生成された設定ファイルや依存管理は削除する
- ログに接続先(サーバー名・DB 名)を一度出力し、本番で何に繋いでいるかを目で確認する
Microsoft Entra ID(旧 Azure AD)を使う場合の本番設定ポイント
認証・認可に Microsoft Entra ID を使うと、Angular 側と ASP.NET Core 側の両方で設定が必要になります。ローカル開発用の設定をそのまま本番に持ち込むと、トークン検証失敗 → 401/403 → それをハンドリングしきれず 500 というパターンも起こりがちです。
Angular(フロントエンド)側のポイント
- MSAL(@azure/msal-browser / @azure/msal-angular)を利用している場合、本番用の リダイレクト URI を Entra のアプリ登録に追加
http://localhost:4200だけでなく、https://frontend.example.comのような本番ドメインを登録する- スコープには API 用アプリ登録で公開した権限(例:
api://<client-id>/API.Access)を指定
ASP.NET Core API 側のポイント
- API 用に別のアプリ登録を作成し、「スコープの公開」で SPA から利用するスコープを定義
- API 側の
Issuer/Audience(ValidIssuer,ValidAudience)を本番のテナント/アプリ ID URI に合わせる appsettings.Development.jsonとappsettings.Production.jsonなどで、テナント ID やクライアント ID を環境ごとに切り替える- App Service の構成で
ASPNETCORE_ENVIRONMENT=Productionを明示する
ここでよくあるのが、「Issuer や Audience が localhost 用のまま」という問題です。この状態でもローカルでは動きますが、本番環境ではトークン検証が失敗し、認証ミドルウェアで例外が出て 500 に繋がるケースがあります。ログに IDX で始まるエラーコード(トークン検証エラー)が出ていないか確認しましょう。
CORS 設定の最小構成と落とし穴
質問の前提では「CORS は解消済み」となっていますが、誤った CORS 設定が結果的に 500 っぽい挙動を生む こともあります。特に、withCredentials 利用時や認証付き API の場合は注意が必要です。
基本方針
- 許可するオリジンは スキーム+ホスト+ポート までをピンポイントで指定する(例:
https://frontend.example.com) Access-Control-Allow-Credentials: trueを返す場合、Access-Control-Allow-Origin: *は絶対に使わない- Angular 側で
withCredentials: trueを付けているかどうかと、サーバー側の設定を揃える
ASP.NET Core の典型的な CORS 設定は次のような形です。
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowFrontend", policy =>
{
policy.WithOrigins("https://frontend.example.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
app.UseCors("AllowFrontend");
本番で 500 が発生している場合でも、まずは本当に CORS が原因でないかを、開発者ツールの Network タブとレスポンスヘッダーを見て再確認すると安心です。
ランタイム・起動設定:ANCM 500.30 / 500.31 を潰す
Windows 版 App Service で ASP.NET Core を動かす場合、ANCM(AspNetCoreModuleV2)による起動が必ず行われます。この際の設定不整合は、500.30 / 500.31 のようなエラーを引き起こします。
App Service のランタイム設定
- App Service の「構成」>「全般設定」で、スタック(.NET / .NET Core)とバージョンを確認
- フレームワーク依存(framework-dependent)発行の場合、App Service 側に対応するランタイムがインストールされている必要がある
- 自己完結(self-contained)発行の場合、ランタイムに依存しないが、web.config の
processPathが実行ファイルを指していることを確認
stdout ログを一時的に有効化する
起動時に何が起きているかを知るために、web.config の stdoutLogEnabled を true に変更して再発行する方法があります。
<aspNetCore processPath="dotnet"
arguments="".\MyApi.dll""
stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout"
hostingModel="inprocess" />
この状態で API にアクセスすると、起動時例外のスタックトレースが D:\home\site\wwwroot\logs\stdout_*.log に書き出されます。原因が特定できたら、ログの肥大化を避けるために stdoutLogEnabled は 必ず false に戻して再デプロイしてください。
実際に有効だった施策:ケーススタディ
ここまでの内容を踏まえ、実際のスレッドで最終的に 500 エラーが解消したときの流れを整理します。環境や要件によって細部は変わりますが、同じ構成のシステムであればかなり再現性の高い手順です。
1. SQL ファイアウォールを調整
- Azure SQL サーバー設定で「Azure サービスおよびリソースからのアクセスを許可」を有効化
- App Service から DB に対してタイムアウトが発生しなくなったことをログで確認
2. Service Connector / Service Dependencies を整理
- Visual Studio で自動生成された Service Dependencies の設定を確認
- 接続文字列を
appsettings.jsonと App Service の「構成」に一本化 - 使っていない接続設定は削除し、「本番用 DB にしか繋がらない」状態にする
3. ADO.NET+Entra ID(パスワードレス)認証を採用
- App Service のシステム割り当てマネージド ID を有効化
- Azure SQL 上で
CREATE USER FROM EXTERNAL PROVIDERとロール付与を実施 - 接続文字列を
Authentication=Active Directory Default;形式に変更 - アプリケーションログで、マネージド ID での接続が成功していることを確認
4. web.config とランタイム設定を確認し、再発行
wwwroot直下に web.config が存在し、AspNetCoreModuleV2が指定されていることを確認- App Service のランタイムバージョンと、プロジェクトのターゲットフレームワークが整合していることを確認
- stdout ログを一時的に有効化して起動時例外が出ないことを確認後、無効化して再発行
これらの対応を行った結果、デプロイ済み API からデプロイ済み Azure SQL への接続が正常化し、HTTP 500 エラーは解消されました。同様の構成で 500 に悩まされている場合は、ほぼ同じ手順で切り分けと解消ができるはずです。
効率よく原因を潰すためのチェックリスト
最後に、同じような構成で 500 エラーに遭遇したときに上から順に確認すると効率が良いチェックリストをまとめます。実際の運用では、このリストを手元に置きながらログと構成を見ていくと、原因特定がかなり楽になります。
- ログ周り
- App Service の「ログ ストリーム」で例外や ANCM エラーが出ていないか
- App Service ログ(アプリケーションログ、Web サーバーログ)が有効になっているか
- Kudu の
D:\home\LogFiles(特にeventlog.xml,stdout_*.log)を確認したか
- web.config
wwwroot直下に web.config が存在するかAspNetCoreModuleV2がハンドラとして設定されているかprocessPath/argumentsが発行内容と一致しているか
- .NET ランタイム・発行設定
- App Service のランタイム(スタック/バージョン)がターゲットと一致しているか
- self-contained / framework-dependent の設定と web.config の内容が一致しているか
- 必要に応じて stdout ログを一時的に有効化し、起動時例外を確認したか
- DB 接続
- Azure SQL で「Azure サービスおよびリソースからのアクセスを許可」が有効か
- 接続文字列が本番用サーバー(
xxx.database.windows.net)と DB 名を指しているか - SQL ログイン/マネージド ID/Entra ユーザーに必要な DB 権限を付与しているか
- 設定の単一化
- Service Connector / Service Dependencies と appsettings.json が二重管理になっていないか
- 本番で実際にどの接続文字列が使われているかをログに出して確認したか
- 認証・認可(Microsoft Entra ID)
- Angular のリダイレクト URI が本番ドメインを含んでいるか
- API の Issuer / Audience が本番テナント・本番アプリ ID に向いているか
ASPNETCORE_ENVIRONMENTをProductionに設定し、設定ファイルの切り替えができているか
- CORS
- 本番オリジン(
https://...)をピンポイントで許可しているか withCredentialsの有無とサーバー側の Allow-Credentials 設定が一致しているか- Network タブで CORS エラーになっていないことを目視確認したか
- 本番オリジン(
まとめ:ローカルでは動くのに本番で 500 になるときの考え方
Angular+ASP.NET Core+Azure SQL のようなモダンな構成では、「ローカルでは動くのに Azure にデプロイすると HTTP 500」という事象が発生しがちです。しかし、その多くは次のどれかに集約できます。
- IIS/ANCM レベルの起動エラー(web.config やランタイム不整合)
- Azure SQL への接続失敗(ファイアウォール・権限・接続文字列の問題)
- Microsoft Entra ID の本番設定漏れ(Issuer/Audience やリダイレクト URI の不整合)
- CORS や Cookie/トークン周りの設定ミス
闇雲に設定をいじるのではなく、まずはログをしっかり有効化して「どこで」「どの層で」例外が出ているのかを特定することが、最短での解決への近道です。そのうえで、本記事のチェックリストに沿って一つ一つ潰していけば、多くの 500 エラーは再現・解消できるはずです。
同じような構成で Azure へのデプロイを検討している場合は、設計段階から「本番では Entra ID のマネージド ID を使う」「接続文字列の管理場所を明確に決める」「web.config とランタイムの関係を理解しておく」といったポイントを意識しておくと、後からのトラブルシュートがぐっと楽になります。

コメント