ASP.NET Core本番でSwagger UIが表示されない時の原因と対処|EC2・IIS・Nginx・ALB対応の完全ガイド

ローカルの Visual Studio では Swagger UI が見えるのに、本番サーバー(EC2/IIS/Nginx/ALB など)では /swagger/index.html が 404——。この落差は「コードの不具合」ではなく、ASP.NET Core の既定動作と配備構成の差で起きることがほとんどです。この記事では、最短の修正から構成別の対処、セキュリティ強化、診断コマンドまでを一気通貫で解説します。

目次

現象の再整理(よくある訴求)

  • ローカル(https://localhost:7173/swagger/index.html など)では表示される。
  • EC2/Linux や Windows Server に配備すると https://<ドメイン>/swagger/index.html が 404。
  • 以前は本番でも見えたという話があるが、現在は設定が不明。
  • サーバー側で Swagger UI を起動・閲覧したい。

結論(最短の修正)

ASP.NET Core のテンプレートでは Swagger ミドルウェアが 開発環境限定で有効化されています。Production でも常時有効にするなら、Program.cs で環境条件分岐を外してください。

// 既定のテンプレート(開発時のみ)
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

// → 本番でも常時有効にする場合(推奨は後述の制限付き公開)
app.UseSwagger();
app.UseSwaggerUI(); // 必要に応じてオプションを設定 

前提として Program.cs に次が含まれている必要があります(テンプレート既定)。

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

再ビルド・再デプロイ後、/swagger/index.html にアクセスできるようになります。

なぜ本番だけ表示されないのか(仕組みの要点)

  • テンプレートの既定は「開発時のみ Swagger を出す」。本番では API 仕様の露出を避けるために無効。
  • 本番サーバーの ASPNETCORE_ENVIRONMENT は通常 Production。条件 IsDevelopment() に合致しないため Swagger ミドルウェアが配線されない。
  • さらに、リバースプロキシ(Nginx/IIS/ALB)で パスが書き換わる と、/swagger へのルーティングが失敗して 404 になります。

5分で終わる原因切り分けチェックリスト

観点確認コマンド / 方法期待/判定対処の方向性
環境モードprintenv ASPNETCORE_ENVIRONMENT(Linux)/ システム環境変数(Windows)Production ならテンプレ既定で無効UseSwagger() を常時有効 or 制限付き公開に変更
Swagger JSONcurl -I https://<ドメイン>/swagger/v1/swagger.json200 が返る返らない場合はミドルウェア未配線 or パスベース不整合
パスベース配備 URL が /api 等のサブパスかアプリの実パスと一致UsePathBase() と RoutePrefix/SwaggerEndpoint を整合
HTTPS 判定ALB/Nginx 経由で https → http の場合UI 内の JSON パスが https/相対パスで解決UseForwardedHeaders() と相対パス指定を徹底
リバースプロキシNginx/IIS のリライト設定/swagger/ が書き換えられない先頭で除外ロケーション/ハンドラを追加
パッケージプロジェクトの参照Swashbuckle.AspNetCore が参照済み不足なら復旧(テンプレなら既定で入る)

最短のコード例(.NET 8 / Minimal Hosting)

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// 逆プロキシ越しの HTTPS 判定を正しくする(ALB/Nginx 配備時は強く推奨)
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor
});

app.UseSwagger();
app.UseSwaggerUI(c =>
{
// 重要:相対パスで指定(プロキシ配備でも崩れにくい)
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1");
// 必要なら UI の公開パスを変更
// c.RoutePrefix = "docs"; // => /docs で公開
});

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run(); 

Production での公開は「制限付き」を強く推奨

本番で常時公開する場合は、最低限の保護をかけましょう。以下は Swagger パスだけ を簡易 Basic 認証で守る例です(実運用は IP 制限や OIDC による認証を推奨)。

bool IsSwaggerRequest(PathString path) => path.StartsWithSegments("/swagger");

app.UseWhen(ctx => IsSwaggerRequest(ctx.Request.Path), branch =>
{
branch.Use(async (ctx, next) =>
{
string auth = ctx.Request.Headers.Authorization;
if (string.IsNullOrEmpty(auth) || !auth.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
{
ctx.Response.Headers["WWW-Authenticate"] = "Basic realm="Swagger"";
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
// ユーザー名:パスワード(例:swagger:secret)
var expected = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("swagger:secret"));
var token = auth.Substring("Basic ".Length).Trim();
if (!string.Equals(token, expected, StringComparison.Ordinal))
{
ctx.Response.StatusCode = StatusCodes.Status403Forbidden;
return;
}
await next();
});
});

app.UseSwagger();
app.UseSwaggerUI(); 

配備構成別の落とし穴と対処

EC2(Amazon Linux)+ Nginx + Kestrel

典型的な構成では、Nginx が 443 を受けて Kestrel にプロキシします。location / のリライトが強すぎると /swagger/ が SPA の index.html に吸い込まれて 404 風になります。先頭で除外するか、個別ロケーションを追加しましょう。

server {
    listen 443 ssl;
    server_name api.example.com;

```
# 省略: SSL 設定

# Swagger を最優先で素通し
location /swagger/ {
    proxy_pass         http://127.0.0.1:5000/swagger/;
    proxy_set_header   Host $host;
    proxy_set_header   X-Forwarded-Proto https;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
}

# API 全体をプロキシ
location / {
    proxy_pass         http://127.0.0.1:5000;
    proxy_set_header   Host $host;
    proxy_set_header   X-Forwarded-Proto https;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
}
```

} 

また、Program.cs 側では UseForwardedHeaders を有効にして https 判定を正しくし、Swagger UI 内のリンク崩れを防ぎます。

ALB(アプリケーションロードバランサ)でパスベース配備(例:/api)

ALB で /api/* をターゲットグループへ流していると、実アプリは /api を 基底パス として扱う必要があります。

app.UsePathBase("/api");     // 実行アプリの基底パスを /api に
app.UseSwagger();
app.UseSwaggerUI(c =&gt;
{
    // すべて相対パスで記述(/api をハードコードしない)
    c.SwaggerEndpoint("/api/swagger/v1/swagger.json", "API v1");
    c.RoutePrefix = "swagger"; // =&gt; /api/swagger
});

IIS(Windows Server)+ ASP.NET Core Module

Web.config などで環境変数を制御できます。Production でも一時的に開くなら、コードを変えず ASPNETCORE_ENVIRONMENT を Development に切替える手もあります(公開しっぱなしにしないこと)。

&lt;configuration&gt;
  &lt;system.webServer&gt;
    &lt;handlers&gt;
      &lt;add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" ... /&gt;
    &lt;/handlers&gt;
    &lt;aspNetCore processPath="dotnet" arguments="MyApi.dll" stdoutLogEnabled="true" ...&gt;
      &lt;environmentVariables&gt;
        &lt;add name="ASPNETCORE_ENVIRONMENT" value="Production" /&gt;
      &lt;/environmentVariables&gt;
    &lt;/aspNetCore&gt;
  &lt;/system.webServer&gt;
&lt;/configuration&gt;

URL 書き換え(URL Rewrite)や Application Request Routing (ARR) を使っている場合、/swagger/ を除外パターンに含めるのが安全です。

Apache httpd(mod_proxy)

<VirtualHost *:443>
  ServerName api.example.com
  # SSL 設定 省略

ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"

# Swagger を先に通す

ProxyPass        /swagger/ [http://127.0.0.1:5000/swagger/](http://127.0.0.1:5000/swagger/)
ProxyPassReverse /swagger/ [http://127.0.0.1:5000/swagger/](http://127.0.0.1:5000/swagger/)

# 他はアプリへ

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/)
 

エラー別トラブルシュート早見表

症状主因対処
404 /swagger/index.htmlミドルウェア未配線 / パス書き換えUseSwagger() を条件外に出す。プロキシで /swagger/ を除外・優先。
404 /swagger/v1/swagger.jsonDoc 名不一致 / バージョニングの設定漏れAddSwaggerGen() の SwaggerDoc("v1"...) と UI の SwaggerEndpoint を合わせる。
UI のボタンを押すと http に飛ぶX-Forwarded-Proto 未設定プロキシでヘッダを付与、アプリで UseForwardedHeaders。
リダイレクトループUseHttpsRedirection とプロキシ設定の不整合プロキシで https を明示、アプリ側はヘッダを信頼。
403/401認証・認可でブロックSwagger パスだけに緩和ルールまたは専用認証を適用。

環境・設定の把握(表で整理)

項目Linux (systemd)Windows (IIS/サービス)ポイント
環境変数/etc/systemd/system/myapi.service の Environment=ASPNETCORE_ENVIRONMENT=Productionシステム環境変数 or web.configDevelopment にするとテンプレ既定で Swagger が有効
ポートASPNETCORE_URLS=http://127.0.0.1:5000IIS が受け持ち(OutOfProcess)直接 80/443 を Kestrel で聴かせない構成が安全
ログjournalctl -u myapi -n 200IIS の stdout ログUI が 404 のときもアプリ側ログを必ず確認

複数ドキュメント・複数環境への対応

API バージョンや環境ごとに OpenAPI を分けたいケースでは、SwaggerDoc を複数登録し、UI 側で切り替えます。

builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new() { Title = "My API", Version = "v1" });
    c.SwaggerDoc("v2", new() { Title = "My API", Version = "v2" });
});

app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1");
c.SwaggerEndpoint("/swagger/v2/swagger.json", "My API v2");
}); 

サブパス配備(/myapp など)での確実な設定

アプリをサブディレクトリに配備するなら、UsePathBase と UI の RoutePrefix を整えます。相対パス指定が鍵です。

app.UsePathBase("/myapp");
app.UseSwagger();
app.UseSwaggerUI(c =&gt;
{
    c.RoutePrefix = "swagger"; // =&gt; /myapp/swagger
    c.SwaggerEndpoint("/myapp/swagger/v1/swagger.json", "API v1");
});

設定をコードではなく構成で切り替える(おすすめ)

「一部の本番だけ許可したい」をコード分岐で書かず、構成値で切り替えるのが運用しやすいです。

{
  "Swagger": {
    "Enabled": false,
    "RoutePrefix": "swagger"
  }
}
var swaggerSection = builder.Configuration.GetSection("Swagger");
bool swaggerEnabled = swaggerSection.GetValue("Enabled", false);
string routePrefix = swaggerSection.GetValue("RoutePrefix", "swagger");

if (swaggerEnabled)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.RoutePrefix = routePrefix;
c.SwaggerEndpoint($"/{routePrefix}/v1/swagger.json", "API v1");
});
} 

環境ごとに構成ファイルや環境変数で Swagger:Enabled=true にすれば、コードを触らず公開・非公開を切り替えられます。

.NET 6 以前(Startup クラス)の例

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddEndpointsApiExplorer();
        services.AddSwaggerGen();
    }

```
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 本番含め常時
    app.UseSwagger();
    app.UseSwaggerUI(c =&gt;
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1");
    });

    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =&gt; { endpoints.MapControllers(); });
}
```

} 

セキュリティの勘所(公開リスクと対策)

  • Swagger は 全エンドポイントとスキーマ を可視化します。攻撃者の踏み台情報にもなり得るため、外部公開時は保護必須。
  • IP 制限:プロキシで社内/VPN のみ許可。
  • 認証:OIDC/OAuth2(社内 IdP)や簡易 Basic 認証でガード。
  • 別環境切替:Staging のみ有効化して参照用にする運用。
  • ビルド分離:CI/CD のプロファイルごとに Swagger:Enabled を制御。

診断のための定番コマンド集

# Linux: 環境変数とリッスン確認
printenv | grep ASPNETCORE
ss -lntp | grep 5000

# systemd: サービス定義/ログ

systemctl cat myapi.service
journalctl -u myapi -n 200

# 動作確認

curl -I https://<ドメイン>/swagger/index.html
curl -I https://<ドメイン>/swagger/v1/swagger.json

# 証明書/プロキシ経路の確認(詳細)

curl -vk https://<ドメイン>/swagger/ 

チェック済みデプロイ手順(安全第一の標準案)

  1. コード:UseSwagger() と UseSwaggerUI() を常時配線。UseForwardedHeaders を有効化。
  2. 構成:Swagger:Enabled を環境変数で制御。Production は基本 false。
  3. プロキシ:/swagger/ の除外ロケーション(Nginx/Apache/IIS)を追加。X-Forwarded-Proto を付与。
  4. 認可:Swagger パスに限定して認証/認可を適用(Basic/OIDC/IP 制限)。
  5. 検証:/swagger/index.html と /swagger/v1/swagger.json を curl -I で確認。
  6. 監査:公開範囲(社外/社内)を明文化し、運用手順に組み込み。

よくある「それでも見えない」ケースと解決のコツ

  • UI は 200 だがリソースが 404:UI が参照する swagger-ui.css などはミドルウェアが埋め込みリソースとして配信します。プロキシが *.css を別ハンドラに回していないか確認。
  • API Versioning を導入後に 404:SwaggerDoc("v1"...) と [ApiVersion("1.0")] の対応を見直し、DocInclusionPredicate を設定。
  • SPA と共存:UseSpa() やフロントの try_files $uri /index.html が /swagger を飲み込むことがある。除外を先頭に書く。
  • コンテナ配備:ASPNETCORE_URLS と公開ポートのマッピングを再点検。ヘルスチェックのパスを /swagger/v1/swagger.json にした場合、保護を忘れない。

まとめ

本番サーバーで Swagger UI が 404 になる最大要因は、テンプレ既定の「開発時のみ有効」とプロキシ配備のパス不整合です。UseSwagger()/UseSwaggerUI() を常時配線し、相対パス指定・UseForwardedHeaders・プロキシ側の /swagger/ 除外をセットで行えば、多くの環境で安定して表示できます。公開は最小限にとどめ、IP 制限や認証で保護するのが本番運用の定石です。ここまでの手順とチェックリストを踏めば、EC2/IIS/Nginx/ALB のどれでも短時間で復旧できます。


付録:テンプレの if ブロックを残したまま本番だけ開く(安全寄り)

if (app.Environment.IsDevelopment() || builder.Configuration.GetValue("Swagger:Enabled", false))
{
    app.UseSwagger();
    app.UseSwaggerUI(c =&gt; c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"));
}

環境変数 Swagger__Enabled=true(ダブルアンダースコア)で一時公開できます。緊急時の切替に便利ですが、恒常公開は避けましょう。

この記事を書いた人

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

コメント

コメントする

目次