Azure App Service 上の WCF SOAP が HTTP では動くのに HTTPS だけ 404 になる――この現象は、IIS/WCF のバインディング解決と発行後の web.config 変換が絡むと発生しやすい落とし穴です。原因の見極めと確実な修正方法を、実運用を想定して手順・検証ポイントまで詳しく解説します。
Azure App Service に配置した WCF SOAP サービスで「HTTPS のみ 404」になる原因と解決ガイド
以下の条件で発生する「HTTPS に切り替えた瞬間だけ 404」問題について、原因の仕組み、設定例、検証手順、運用での注意点までを網羅します。この記事は ASP.NET(.NET Framework)上の WCF SOAP を前提にしていますが、IIS のハンドラ解決と WCF のプロトコル・バインディングの関係を理解することで、類似の事象にも応用できます。
発生する現象
https://<site>.azurewebsites.net/MySVC.svc?wsdlはブラウザで表示できる(WSDL が取得できる)。- しかし SOAP クライアント(または自作の HttpClient)から HTTPS でメソッド呼び出しを行うと HTTP 404 を返す。
- 同じエンドポイントを HTTP で呼ぶと成功する。
- ローカル IIS Express では HTTP/HTTPS ともに正常。
環境の前提
| 項目 | 内容 |
|---|---|
| ホスティング | Azure App Service(Windows、IIS 管理) |
| ドメイン/証明書 | *.azurewebsites.net(無料証明書) |
| アプリ種別 | ASP.NET Web アプリ(WCF SOAP サービスを同居) |
| 通信 | HTTP は成功、HTTPS は 404 |
| コード属性 | [WebInvoke] が付いているが SOAP エンドポイントとして利用 |
| 設定配布 | 発行後に web.config は手動変更なし(CI/CD による Transform の可能性あり) |
なぜ「HTTPS だけ 404」になるのか(しくみの整理)
Azure App Service ではフロントで TLS 終端が行われ、アプリ側(IIS/WCF)には 「HTTPS スキームのリクエストとして到達」します。WCF は受け取ったスキーム(http/https)に対して、<protocolMapping> や エンドポイントのバインディング設定を手がかりに、どの binding を用いるかを解決します。
ここで https スキームに対応する binding が解決できない、あるいは 構成の定義位置が不適切で無効になっていると、IIS は当該リクエストを正しくサービスへ委譲できず 404 を返すことがあります。ローカル環境では Visual Studio のテンプレートや IIS Express の既定が補ってくれるため再現しないことがあり、App Service に発行して初めて露見します。
結論:<protocolMapping> で https→basicHttpsBinding を明示する
HTTPS の要求に対して SOAP バインディングを正しく選択させるには、system.serviceModel 内で次の関連付けを追加(または正しい位置に移動)します。
<system.serviceModel>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<!-- 既存設定 -->
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
scheme="https"をbasicHttpsBindingにマッピングすることで、HTTPS リクエストが正しく SOAP バインディングにルーティングされます。- 記述位置が重要です。
<serviceHostingEnvironment>より後方やファイル末尾に置くと、環境や構成の読み込み順によっては無効化されることがあります。なるべく<system.serviceModel>冒頭付近に記述してください。
HTTP/HTTPS のバインディングを整理する
| スキーム | 推奨バインディング | 補足 |
|---|---|---|
| http | basicHttpBinding | 開発・検証用途。運用は HTTPS を推奨。 |
| https | basicHttpsBinding | WCF 4.5+ で追加。basicHttpBinding+Transport でも類似構成は可能だが、HTTPS では basicHttpsBinding 推奨。 |
Kudu で発行後の web.config を必ず確認する
CI/CD(パイプラインの Transform、スロット設定、パラメータ化など)が介在すると、発行先の web.config がローカルと異なる内容になることがあります。Azure Portal の 「高度なツール(Kudu)」 から site/wwwroot/web.config を直接開き、上記の <protocolMapping> が正しく反映されているかを確認します。
確認の要点
<system.serviceModel>の直下付近に<protocolMapping>...basicHttpsBinding...があるか。- 余計な Transform により、
<protocolMapping>が削除・移動されていないか。 - ステージング/本番スロットで
web.configが一致しているか。 <handlers>や<modules>の差分で .svc ハンドラが無効化されていないか。
完全な構成サンプル(HTTP/HTTPS 併用・SOAP 専用)
以下は、HTTP と HTTPS の両方を公開しつつ、運用は HTTPS 利用を前提としたサンプルです。SOAP 専用を意図しているため、REST 用の [WebInvoke] は省いています(後述の注意参照)。
<configuration>
<system.web>
<compilation debug="false" targetFramework="4.8" />
<httpRuntime targetFramework="4.8" />
</system.web>
<system.serviceModel>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
<add binding="basicHttpBinding" scheme="http" />
</protocolMapping>
<bindings>
<basicHttpsBinding>
<binding name="httpsBinding" maxReceivedMessageSize="5242880">
<readerQuotas maxDepth="32" maxStringContentLength="1048576" />
<security mode="Transport" />
</binding>
</basicHttpsBinding>
<basicHttpBinding>
<binding name="httpBinding" maxReceivedMessageSize="5242880">
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="MyNamespace.MyService" behaviorConfiguration="ServiceBehavior">
<endpoint address="" binding="basicHttpsBinding" bindingConfiguration="httpsBinding" contract="MyNamespace.IMyService" />
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
<!-- http 用の mex が必要なら mexHttpBinding を追加 -->
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.diagnostics>
<sources>
<source name="System.ServiceModel" switchValue="Warning,ActivityTracing">
<listeners>
<add name="xml" />
</listeners>
</source>
</sources>
<sharedListeners>
<add name="xml" type="System.Diagnostics.XmlWriterTraceListener" initializeData="D:\home\LogFiles\WCF.svclog" />
</sharedListeners>
<trace autoflush="true" />
</system.diagnostics>
</configuration>
サービス コードの最小例
namespace MyNamespace
{
[ServiceContract]
public interface IMyService
{
[OperationContract]
string Echo(string value);
}
public class MyService : IMyService
{
public string Echo(string value) => $"Echo:{value}";
}
}
検証手順(HTTP → HTTPS 切替で差分を確認)
- ブラウザで
https://<site>.azurewebsites.net/MySVC.svc?wsdlにアクセスして WSDL 表示を確認。 - SOAP クライアント(SoapUI など)で HTTP 呼び出しを試し、成功を確認。
- HTTPS に切り替えて同じメソッドを呼び出し、404 が再現するか確認。
<protocolMapping>を追加/修正して再発行。- Kudu で発行後の
web.configに反映されているか確認。 - 再度 HTTPS で呼び出し、200 応答と SOAP ボディを確認。
PowerShell での最小呼び出し例
簡易確認用に、HTTP/HTTPS へ SOAP エンベロープを投げる例を載せます。
$uri = "https://<site>.azurewebsites.net/MySVC.svc"
$envelope = @"
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<Echo xmlns="http://tempuri.org/">
<value>hello</value>
</Echo>
</s:Body>
</s:Envelope>
"@
Invoke-WebRequest -Uri $uri -Method Post -ContentType "text/xml; charset=utf-8" `
-Headers @{"SOAPAction"="http://tempuri.org/IMyService/Echo"} `
-Body $envelope -UseBasicParsing
よくある落とし穴と対策
[WebInvoke] と SOAP の混在
[WebInvoke] は本来 REST(WebHttpBehavior)向けです。SOAP 専用であれば [OperationContract] のみで十分です。混在させる場合は endpoint を webHttpBinding(REST)と basicHttpsBinding(SOAP)で分け、それぞれの behaviorConfiguration を明確に切り分けます。混在が中途半端だと、HTTPS だけ 404/405 になることがあります。
protocolMapping の記述位置
<protocolMapping> を <serviceHostingEnvironment> より後や、別の構成セクションの外に置くと無視されることがあります。必ず <system.serviceModel> の先頭付近に置いてください。
Transform による意図しない変更
Release 用の Web.Release.config で xdt:Transform="Replace" を使うと、開発環境で入れていた <protocolMapping> が丸ごと削除されるケースがあります。Replace を避け、SetAttributes や InsertIfMissing を使う、あるいは protocolMapping セクションを別ファイル化して configSource で参照するなど、変更範囲を限定しましょう。
ハンドラ/モジュールの無効化
<system.webServer> の <handlers> で .svc のハンドラが無効化されていると 404 になります。App Service では既定で有効ですが、誤って無効化していないか確認します。
トラブルシューティングの観点一覧
| 観点 | 確認ポイント | 期待状態 |
|---|---|---|
| プロトコル→バインディング解決 | <protocolMapping> に https/basicHttpsBinding | HTTPS リクエストが SOAP に到達 |
| メタデータ公開 | serviceMetadata httpsGetEnabled="true" | WSDL が HTTPS で取得可 |
| エンドポイント | contract/binding の対応が正しいか | 契約とバインディングが一致 |
| Transform | 発行後の web.config を Kudu で確認 | 差分なし/意図どおり |
| 証明書 | *.azurewebsites.net 利用時は追加不要 | 独自ドメイン時のみバインド要 |
| ログ | WCF Trace(svclog)、App Service 診断ログ | 404/500 の原因追跡が可能 |
WCF トレースと App Service ログで根因を特定する
再現が断続的な場合、ログを併用して差分を可視化します。
- WCF トレース:前掲の
System.ServiceModelトレースをD:\home\LogFiles\WCF.svclogに出力。Kudu からダウンロードして SvcTraceViewer で解析。 - アプリケーション ログ:
D:\home\LogFiles\Applicationに .NET の例外情報を出力(web.configのsystem.diagnosticsやtraceを活用)。 - HTTP ログ:App Service の「診断設定」で HTTP ログを有効化し、404 のリクエスト PATH/ヘッダを確認。
REST と SOAP を両立させる設計の例
同一アプリで REST と SOAP を併用する場合は、エンドポイントを明確に分離します。
<services>
<service name="MyNamespace.MyService" behaviorConfiguration="ServiceBehavior">
<!-- SOAP -->
<endpoint address="soap" binding="basicHttpsBinding" contract="MyNamespace.IMyService" />
<!-- REST -->
<endpoint address="rest" binding="webHttpBinding" behaviorConfiguration="restBehavior" contract="MyNamespace.IRestApi" />
</service>
</services>
これにより、/MySVC.svc/soap が SOAP、/MySVC.svc/rest が REST と明確化され、404/405 の混乱を避けられます。
CI/CD で安全に構成を反映するコツ
- 構成の分割:
protocolMappingやbindingsをconfigSourceで外部化し、環境別ファイルで管理。 - Transform の粒度最小化:
Replace乱用を避け、必要最小限の属性変更に限定。 - ステージングで検証:スロットに発行してからスワップ。Kudu での差分チェックをパイプラインに自動化。
HTTP 404/405/500 の見分け方
| コード | 典型原因 | 対処 |
|---|---|---|
| 404 | ハンドラ未解決、エンドポイント不一致、protocolMapping 無効 | protocolMapping とエンドポイント定義、ハンドラ設定を点検 |
| 405 | メソッド(POST/GET)不一致、REST/SOAP の混在 | SOAP は基本 POST。REST は webHttpBinding を専用化 |
| 500 | シリアライザ例外、バインディング側最大値超過 | maxReceivedMessageSize、readerQuotas を調整 |
独自ドメインやクライアント証明書を使う場合の補足
- 独自ドメイン:App Service の TLS/証明書バインドを行えば、構成は同じく
basicHttpsBindingで動作します。 - クライアント証明書(mTLS):App Service でクライアント証明書を受け取り、WCF 側では
TransportセキュリティとclientCredentialType="Certificate"を設定します(本記事の 404 問題とは別レイヤー)。
チェックリスト(最短で直す)
<system.serviceModel>直下に<protocolMapping><add scheme="https" binding="basicHttpsBinding"/></protocolMapping>を追加。serviceMetadataのhttpsGetEnabled="true"を確認。- エンドポイントの
binding="basicHttpsBinding"とcontract名の一致を確認。 - Kudu で発行先
web.configを確認(Transform による改変の有無)。 - 必要に応じて WCF トレースと HTTP ログを有効化。
FAQ
WSDL は HTTPS で表示できるのに、呼び出しだけ 404 になるのはなぜ?
WSDL の配信は メタデータ公開 の設定(serviceMetadata)が担います。一方、実処理の呼び出しは エンドポイント+バインディング の解決が必要です。後者が HTTPS で正しく解決できないと 404 になります。
basicHttpBinding に security mode="Transport" を設定すれば十分では?
動作は可能です。ただし近年は basicHttpsBinding がシンプルで確実です。protocolMapping で https→basicHttpsBinding を明示しておくと、構成の見通しと保守性が上がります。
App Service のフロントで TLS 終端されるなら、アプリ側は HTTP と見なされる?
App Service はアプリに対し HTTPS スキームとしてリクエストを渡します(X-Forwarded-Proto: https 等)。よって WCF では https スキームとしてのバインディング解決が必要になります。
ローカルでは再現しないのに本番だけ 404 です
多くは Transform や配置順の差異が原因です。発行後の web.config を Kudu で直接確認し、差分を潰してください。
まとめ
Azure App Service で WCF SOAP を HTTPS で公開する際の 404 は、ほとんどが https スキームと SOAP バインディングの関連付け不足や 構成の無効化(配置位置の問題や Transform)によって起きます。<protocolMapping> による https → basicHttpsBinding の明示、Kudu での発行後チェック、ログによる根因追跡の三点を徹底すれば、安定して HTTPS でも 200 応答を返せるはずです。既存システムの延命のみならず、運用の見える化・再現性を高める観点でも有効です。
実装テンプレート(貼り付け用)
<system.serviceModel>
<protocolMapping>
<add scheme="https" binding="basicHttpsBinding" />
<add scheme="http" binding="basicHttpBinding" />
</protocolMapping>
適用後の確認ポイント(再掲)
| 確認 | 期待する結果 |
|---|---|
| WSDL の取得(HTTPS) | WSDL が表示できる |
| SOAP 呼び出し(HTTPS) | 200 と有効な SOAP 応答 |
| Kudu で web.config | <protocolMapping> が所定位置にある |
| ログ | svclog と HTTP ログに異常がない |
これで直った(事例の要約)
実際に、<protocolMapping> の追加(または正しい位置への移動)を行い、Kudu でデプロイ先 web.config を確認したところ、HTTPS での呼び出しも正常応答となり 404 は解消されました。HTTP と HTTPS の双方で一貫したエンドポイント設計と構成管理が、再発防止の鍵です。

コメント