ASP.NETのWebアプリで「Server Error in ‘/’ Application – HTTP 404. The resource cannot be found.」が出ると、原因が見えづらく復旧に時間がかかりがちです。本記事では Teacher/Receipt.aspx?ReferenceNo=... を例に、IIS/ルーティング/配置の各層で起こり得る理由を網羅し、すぐ試せる具体的な確認方法と修正例、再発防止までを実務目線で整理します。
HTTP 404の正体を最初に押さえる
HTTP 404 は「サーバーには到達したが、要求されたリソースが見つからない」ことを示すクライアント向けのステータスです。ASP.NET の場合、物理ファイルが無い、ルートに一致しない、IIS のハンドラ/モジュールが遮断、あるいはアプリ側が意図して 404 を返す(例:存在しない ReferenceNo)のいずれかに大別できます。まずは IIS のサブステータスで、どの層で弾かれているのかを素早く当てます。
IIS 404 サブステータス早見表
| sc-status.sc-substatus | おおよその意味 | 見るべき場所 |
|---|---|---|
| 404.0 | 一般的な 404(物理パス不在・ルート未一致・アプリからの 404 含む) | 物理ファイル/ルーティング/アプリログ |
| 404.2 | ISAPI/CGI 制限などのポリシーで拒否 | IIS「ISAPI/CGI の制限」 |
| 404.3 | MIME マップ不備(拡張子のハンドラが無い) | IIS「ハンドラマッピング」 |
| 404.5 | URL に禁止された文字列 | Request Filtering |
| 404.7 | 拡張子が Request Filtering で拒否 | web.config の <requestFiltering> |
| 404.11 | 二重エンコードの拒否など | Request Filtering の設定 |
まず潰す:7つの即効チェック
| 調査ポイント | 具体的な確認・対処方法 |
|---|---|
| URL 誤記 | パス/拡張子/クエリ文字列のタイポを再確認。Receipt.aspx と Receipts.aspx の取り違え、Teacher フォルダ階層を誤っていないか。ブックマークや外部リンクはコピー&ペーストで検証。 |
| 物理ファイル・配置 | Teacher/Receipt.aspx が発行先に存在するか。Web アプリケーションなら .aspx は「ビルド アクション: Content」でプロジェクトに含める。発行プロファイルで「発行時に事前に削除」が有効だと古いファイルが消えるので注意。 |
| ルーティング設定 | MVC/Web API/WebForms Friendly URLs を使用しているなら、Teacher/Receipt または Teacher/Receipt.aspx にマッチするルートがあるか。レガシーURL救済用のルートを追加。 |
| 実行時 404(データ欠損) | ReferenceNo が DB に無い場合に、アプリが Response.StatusCode=404 を返していないか。該当コードにログを追加して真因を識別。 |
| 誤ったサーバー/サイト | IIS 複数サイト/スロット構成で、意図したサイトにデプロイされたか。バインドのホスト名・ポート・SNI を再確認。 |
| IIS 設定 | .aspx を処理するハンドラ(PageHandlerFactory)が有効か。<modules> の認証モジュールで 404 化していないか。IIS ログでサブステータスを確認。 |
| デバッグ支援 | web.config に <customErrors mode="Off"/> と <compilation debug="true"/> を一時設定。ブラウザの Network タブや Fiddler で実ステータスと本文を確認。 |
ケース別:原因と具体的な直し方
1) WebForms(.aspx)ページが見つからない/処理されない
物理ファイルの有無をまず確定する
- IIS マネージャーでサイトを右クリック → エクスプローラー →
Teacher/Receipt.aspxの実在を確認。 - 発行元プロジェクトで Receipt.aspx の「ビルド アクション: Content」「出力ディレクトリにコピー: しない(通常で可)」を確認。プロジェクトに未包含だと発行されません。
- プリコンパイル発行を使用する場合は「この事前コンパイル済みサイトを更新可能にする」を有効にしないと、.aspx が物理的に無く
.compiledのみになる構成もあります。構成に応じて発行手法を統一。
.aspx を処理するハンドラが生きているか
IIS の「ハンドラ マッピング」で PageHandlerFactory-Integrated(パス *.aspx)が「有効」になっているか確認。無効化/削除されていると 404.3/404.7 になり得ます。web.config 側で誤って上書きしていないかも点検します。
<configuration>
<system.webServer>
<handlers>
<add name="PageHandlerFactory-Integrated"
path="*.aspx"
verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS"
preCondition="integratedMode"
type="System.Web.UI.PageHandlerFactory" />
</handlers>
</system.webServer>
</configuration>
Request Filtering(要求フィルタ)の拒否を解除
URL に禁止文字や二重エンコードがあると 404.5/404.11 が返ります。今回の例の ReferenceNo=D170102432 は安全ですが、別画面から遷移する際に %2f 等が混入するとブロック対象です。必要に応じて以下のように緩和します。
<configuration>
<system.webServer>
<security>
<requestFiltering allowDoubleEscaping="true">
<requestLimits maxQueryString="4096" />
<fileExtensions>
<remove fileExtension=".aspx" />
<add fileExtension=".aspx" allowed="true" />
</fileExtensions>
</requestFiltering>
</security>
</system.webServer>
</configuration>
ページ自体は存在するが、アプリが 404 を返す
「ページはあるのに 404」は、アプリコードでデータ未検出時に 404 を明示している典型例です。調査のため、取得ロジック直後にログを入れて真因を確定します。
<%@ Page Language="C#" Inherits="WebApp.Teacher.Receipt" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
var referenceNo = Request.QueryString["ReferenceNo"];
Log.Info($"Receipt.aspx hit: ReferenceNo={referenceNo}");
var model = Repository.GetReceipt(referenceNo);
if (model == null)
{
// ここで 404 を返している可能性
Response.StatusCode = 404;
Response.TrySkipIisCustomErrors = true; // IIS 側の既定 404 に置き換えられないように
Response.Write("レシートが見つかりません。ReferenceNo=" + Server.HtmlEncode(referenceNo));
Response.End();
return;
}
// 通常描画...
}
</script>
要件次第では、404 ではなく「結果 0 件」の専用画面を 200 で返す方がユーザビリティは高まります。
2) MVC/Web API で .aspx URL にアクセスしている
アプリが MVC 化されていて、外部の古いリンクが Receipt.aspx を指しているケースでは「ルート未一致の 404」が起きます。レガシー URL を受け止めるルートを 1 行追加すれば解決します。
// RouteConfig.cs
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// 旧URL: /Teacher/Receipt.aspx?ReferenceNo=123 を救済
routes.MapRoute(
name: "LegacyReceiptAspx",
url: "Teacher/Receipt.aspx",
defaults: new { controller = "Teacher", action = "Receipt" }
);
// 現行URL: /Teacher/Receipt?ReferenceNo=...
routes.MapRoute(
name: "Receipt",
url: "Teacher/Receipt",
defaults: new { controller = "Teacher", action = "Receipt" }
);
// 既定
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
属性ルーティング派ならアクション側に直接付与します。
public class TeacherController : Controller
{
[HttpGet]
[Route("Teacher/Receipt")]
[Route("Teacher/Receipt.aspx")] // レガシー互換
public ActionResult Receipt(string ReferenceNo)
{
if (string.IsNullOrEmpty(ReferenceNo)) return HttpNotFound();
var vm = service.FindReceipt(ReferenceNo);
if (vm == null) return HttpNotFound();
return View(vm);
}
}
View 側に .aspx を使っていない MVC なら、URL に .aspx を含めない方針に寄せるのが理想です。どうしても外部リンクが変えられないなら、URL Rewrite や ルートで吸収しましょう。
3) 誤ったサイト/スロットに発行されている
IIS に複数サイト/バインドがある場合、意図と違うサイトにデプロイされると「対象サイトにはファイルが無い=404」です。以下を確認してください。
- IIS サイトの バインドでホスト名/ポート/SNI 設定が要求に合致するか。
- リバースプロキシやロードバランサで、別スロットへ振り分けていないか。
- 発行プロファイルの「発行先 URL」「サイト名」「ユーザー名」がターゲットと一致するか。
ログ&トレースで「どこで」無くなったかを確定
IIS アクセスログを最速で読む
IIS 既定のログは %SystemDrive%\inetpub\logs\LogFiles\W3SVC{ID}\。以下のように cs-uri-stem で Receipt.aspx を、cs-uri-query で ReferenceNo を検索します。
# 例: PowerShell で Receipt.aspx を含む行を抽出
Get-ChildItem "C:\inetpub\logs\LogFiles\W3SVC*\u_ex*.log" |
Select-String "Teacher/Receipt.aspx" |
Select Line | ForEach-Object { $_.Line }
ログの末尾の sc-status(例:404)、sc-substatus(例:3)、sc-win32-status を確認し、前掲の表に当てはめて層を絞ります。
Failed Request Tracing(FREB)で 404 の内部を可視化
- IIS マネージャー → サイト選択 → 失敗した要求のトレース → 有効化。
- ルールの追加 → ステータスコードに 404、すべての拡張子、すべての動詞。
- 再現後、
%SystemDrive%\inetpub\logs\FailedReqLogFilesに出力された .xml を開き、どのモジュール(例:RewriteModule、UrlAuthorization)で 404 になったかを特定。
アプリケーションログの仕込み
「実行時 404」切り分けのため、Receipt に到達したこと/入力パラメータ/DB ヒット件数を構造化ログに出しましょう。
// Serilog 例
Log.Information("Receipt request {@payload}", new { ReferenceNo, User = User.Identity.Name });
var model = repo.GetReceipt(ReferenceNo);
Log.Information("Receipt query result {@result}", new { Found = model != null });
配置・ビルドでハマりやすい落とし穴
ファイルが発行されない/消える
- プロジェクトに .aspx が「含まれていない」(ソリューション エクスプローラー上に薄いアイコン)→ 発行対象に入らない。
- 発行時「事前に削除」を有効にして一部フォルダを除外指定していない → 想定外の .aspx が消える。
- CI/CD で
dotnet publish/MSBuild パラメータが環境で異なる → 本番だけファイル構成がズレる。
プリコンパイルの理解不足
Web サイトをプリコンパイルすると 更新不可 モードでは .aspx が無く、.compiled に置き換わります。IIS のハンドラや権限設定が「物理 .aspx 前提」だと 404 になります。運用方針に合わせて「更新可能」を選ぶか、IIS 側の設定を合わせましょう。
セキュリティ設定が 404 を装うケース
認証/承認で 401/403 の代わりに 404 を返すポリシーを取っている場合があります(情報漏洩防止)。ログイン未済やロール不一致で Receipt を隠匿していると、ユーザーからは単なる 404 に見えます。web.config の承認設定を点検します。
<configuration>
<system.web>
<authorization>
<deny users="?" /> <!-- 匿名拒否 -->
<allow roles="Teachers" />
<deny users="*" />
</authorization>
</system.web>
<location path="Teacher/Receipt.aspx">
<system.web>
<authorization>
<allow users="*" /> <!-- 必要ならここだけ匿名許可 -->
</authorization>
</system.web>
</location>
</configuration>
具体例で学ぶ:Receipt を確実に表示する最低限の設定
WebForms の最少構成
// Teacher/Receipt.aspx.cs
namespace WebApp.Teacher {
public partial class Receipt : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
var id = Request.QueryString["ReferenceNo"];
if (string.IsNullOrWhiteSpace(id))
{
Response.StatusCode = 400;
Response.Write("ReferenceNo が未指定です。");
Response.End();
return;
}
var receipt = Repo.GetReceipt(id);
if (receipt == null)
{
// 404 にするか、404 ではなく「存在しません」画面にするかは要件で決める
Response.StatusCode = 404;
Response.TrySkipIisCustomErrors = true;
Response.Write($"ReferenceNo={Server.HtmlEncode(id)} のレシートは存在しません。");
Response.End();
return;
}
// ラベル等にバインドして描画…
}
}
}
MVC でレガシー .aspx URL を受ける
[RoutePrefix("Teacher")]
public class TeacherController : Controller
{
[HttpGet, Route("Receipt")]
[HttpGet, Route("Receipt.aspx")] // 旧URL互換
public ActionResult Receipt(string ReferenceNo)
{
if (string.IsNullOrEmpty(ReferenceNo)) return new HttpStatusCodeResult(400);
var receipt = service.Find(ReferenceNo);
if (receipt == null) return HttpNotFound();
return View(receipt);
}
}
PowerShell/appcmd でサーバー現地調査を加速
ファイルの存在と更新日時を即確認
Test-Path "D:\Sites\YourSite\Teacher\Receipt.aspx"
Get-Item "D:\Sites\YourSite\Teacher\Receipt.aspx" | Select-Object FullName, Length, LastWriteTime
ハンドラマッピングに .aspx があるか
%windir%\system32\inetsrv\appcmd list config "Default Web Site" ^
-section:system.webServer/handlers /text:*.aspx
Request Filtering の拡張子許可状況
%windir%\system32\inetsrv\appcmd list config "Default Web Site" ^
-section:system.webServer/security/requestFiltering
ユーザー体験を守る:カスタム 404 と観測性
カスタム 404 ページ(ユーザー向け)
ユーザーには「見つからない」理由と次の行動(トップに戻る、入力を再確認、問い合わせ)を提供しましょう。
<configuration>
<system.web>
<customErrors mode="On" defaultRedirect="~/Errors/GeneralError.aspx">
<error statusCode="404" redirect="~/Errors/NotFound.aspx"/>
</customErrors>
</system.web>
<system.webServer>
<httpErrors errorMode="DetailedLocalOnly">
<remove statusCode="404" />
<error statusCode="404" path="/Errors/NotFound.aspx" responseMode="ExecuteURL" />
</httpErrors>
</system.webServer>
</configuration>
開発者向けに 404 情報を残す
- 404 発生時に URL、クエリ、ユーザー、リファラ をアプリログへ記録。
- FREB(404 ルール)を常時オンにせず、発生時だけ有効化できる運用手順を用意。
- APM(アプリ監視)でコール率や 404 率を可視化し、リリース直後に急増があれば即座に検知。
チェックリスト(現場でそのまま使える)
- [ ] その URL をコピー&ペーストで再現したか(ブックマーク・外部リンク依存を排除)。
- [ ] IIS ログで sc-status.sc-substatus を確認し、層(IIS/ルート/アプリ)を特定。
- [ ] 物理ファイル
Teacher/Receipt.aspxの存在、更新日時、アクセス権(IIS AppPool の ID)を確認。 - [ ] ハンドラマッピングで
*.aspxが有効、Request Filtering で拡張子や二重エンコードが拒否されていない。 - [ ] MVC/Web API を使っているなら、
Receipt.aspxを吸収するレガシールートを追加。 - [ ] アプリ側のロジックで
ReferenceNo未検出時に 404 を返していないかログで確定。 - [ ] 複数サイト/スロット誤配備を排除(IIS バインド/CD パイプラインの発行先確認)。
- [ ] 一時的に
<customErrors mode="Off">とdebug="true"で詳細を確認(本番は速やかに戻す)。
原因別:最短修正パターン集
| 症状 | 最短修正 | 副作用/注意 |
|---|---|---|
| 404.3(MIME/ハンドラ不備) | ハンドラに PageHandlerFactory を追加/有効化 | アプリケーションプールは「統合パイプライン」を想定 |
| 404.7(拡張子拒否) | <requestFiltering> で .aspx を許可 | セキュリティレビューで変更を記録 |
MVC で Receipt.aspx へアクセス | レガシールート "Teacher/Receipt.aspx" を追加 | URL 設計を段階的にモダン化 |
| デプロイ漏れ | プロジェクトに .aspx を含めて再発行 | 発行前削除の設定に注意 |
| 実行時 404(データ未検出) | 404 ではなく「結果なし」ページに変更、または入力検証の導線追加 | SEO 的には 404 を維持する設計も一案 |
よくある落とし穴と対処のディテール
大文字小文字の違い
Windows の NTFS は既定で大文字小文字非区別ですが、ルーティングや外部サービスが区別することがあります。ブックマークや QR による /teacher/receipt.aspx と /Teacher/Receipt.aspx の混在は避け、リダイレクトポリシーで正規化しましょう。
URL Rewrite の副作用
美しい URL のための Rewrite ルールが、特定クエリや拡張子で予期せず除外し 404 にする例が多いです。FREB で RewriteModule の各ステップ(Pattern matched / Action)を確認し、Receipt を通す例外ルールを置きます。
<system.webServer>
<rewrite>
<rules>
<rule name="Allow Receipt.aspx" stopProcessing="true">
<match url="^Teacher/Receipt\.aspx$" />
<action type="None" />
</rule>
<!-- 既存ルール... -->
</rules>
</rewrite>
</system.webServer>
アプリケーション プールの設定
.NET CLR バージョン/32bit 有効化/パイプライン モードの不整合で、ハンドラがロードされず 404 風味になることがあります。統合パイプライン + 対応 CLR を選んでください。
デバッグを早める小技
開発環境だけ詳細エラーを出す web.config 変換
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.web xdt:Transform="InsertIfMissing">
<customErrors mode="Off" xdt:Transform="SetAttributes" xdt:Locator="Match(mode)"/>
<compilation debug="true" xdt:Transform="SetAttributes" xdt:Locator="Match(debug)"/>
</system.web>
</configuration>
Network タブでの確認ポイント
- ステータスが 200 なのに画面が空白 → クライアント側 JS で別問題。
- 302 → 404 の遷移 → 認証/ルーティングのリダイレクト経由で 404 化。
- レスポンス本文に「The resource cannot be found.」 → ASP.NET 既定の 404。
再発防止:運用に組み込む 3 つの仕掛け
スモークテストで URL を自動検証
CI/CD に URL チェック(/Teacher/Receipt.aspx、/Teacher/Receipt)を組み込み、200 が返らなければデプロイを失敗させる。バリエーションとして ReferenceNo の存在・非存在ケースで 200/404 の設計通りかも検証しましょう。
リダイレクトとリンク監査
外部に公開済みの .aspx リンクを把握し、恒久的に 301 リダイレクト で新 URL へ案内。アプリ内のリンクは Url.Action 等のヘルパー経由で生成し、手打ち文字列を排除します。
カスタム 404 のログ強化
404 専用ページで リクエスト パス、クエリ、ユーザー、セッションID を記録し、ダッシュボードで「急増」「特定 URL 偏在」を可視化。外部リンク切れを即座に発見できます。
まとめ:手順通りに潰せば 404 は怖くない
HTTP 404 は「届いているが見つからない」という事実の表明で、IIS 設定・ルート・アプリロジックのどこかに原因が必ずあります。本稿のチェックリストに沿って、まず サブステータスで層を特定し、物理ファイル→ハンドラ→ルート→実行時 404 の順に潰してください。レガシーな Receipt.aspx へのリンクが残っているなら、互換ルートを 1 本足すだけで一気にユーザー影響を解消できます。最後に、スモークテストとカスタム 404 のログを運用に組み込めば、同種の不具合は早期に検出・修正可能です。
参考:即投入できるテンプレ集
レガシー URL 吸収(MVC)
routes.MapRoute("LegacyReceiptAspx", "Teacher/Receipt.aspx",
new { controller = "Teacher", action = "Receipt" });
Request Filtering 緩和
<requestFiltering allowDoubleEscaping="true">
<fileExtensions>
<add fileExtension=".aspx" allowed="true" />
</fileExtensions>
</requestFiltering>
カスタム 404
<httpErrors errorMode="DetailedLocalOnly">
<error statusCode="404" path="/Errors/NotFound.aspx" responseMode="ExecuteURL" />
</httpErrors>
ログ仕込み(最小)
Log.Info("Receipt hit {ReferenceNo}", ReferenceNo ?? "(null)");
スモークテスト例(擬似コード)
// 200 想定
GET /Teacher/Receipt.aspx?ReferenceNo=DUMMY-EXISTS -> assert 200
// 404 想定
GET /Teacher/Receipt.aspx?ReferenceNo=DUMMY-NOT-EXISTS -> assert 404
// 旧URL → 新URL 301
GET /Teacher/Receipt.aspx -> assert 301 Location: /Teacher/Receipt
最後に:この記事の使い方
現場では「URL 誤記 → 物理ファイル → ハンドラ → ルート → アプリ 404 → 誤配備」の順で確実に切り分けましょう。詰まったら FREB を一度回す。これだけで 404 調査は劇的に短縮できます。

コメント