🎯 はじめに
AWS WAF を使って 「一時的にサイトをメンテナンス表示にしたい」 と思ったことはありませんか?
本記事では、既存の WAF 構成とは独立して 「メンテナンス専用 WAF」 を作成し、ホワイトリスト以外のアクセスを 503 エラー+メンテナンスページ に切り替える方法を紹介します。
CloudFormationテンプレート付きで、パラメータ 1 つで ON / OFF 切り替え可能です。
🧩 できること
✅ メンテナンスモードを ON にすると、全ユーザーがメンテナンスページへリダイレクト( HTTP 503 )
✅ 特定の IP(社内・ VPN など)だけは通常アクセス許可
✅ メンテナンス解除はパラメータ 1 つ変更するだけ
✅ CloudFront / ALB どちらにも対応( Scope 切替)
✅ HTML をテンプレート内に埋め込み済み(スタイル調整も可能)
🏗️ 構成イメージ
💡 動作の流れ
| 優先度 | ルール名 | 条件 | アクション |
|---|---|---|---|
| 0 | Allow-Whitelist | 許可 IPSet に一致 | Allow |
| 1 | Maintenance-Block | 常に一致( MaintenanceMode = ON の時のみ有効) | Block + 503 + メンテHTML |
⚙️ CloudFormationテンプレート
以下のテンプレートを利用します。
AWSTemplateFormatVersion: "2010-09-09"
Description: Minimal WAF for Maintenance Mode only (whitelist = allow, others = 503 HTML)
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label: { default: WAF Configuration }
Parameters:
- EnvTagPrefix
- ProjTagPrefix
- Scope
- MaintenanceMode
Parameters:
EnvTagPrefix:
Type: String
Default: stg
ProjTagPrefix:
Type: String
Default: app
Scope:
Type: String
Default: CLOUDFRONT
AllowedValues: [REGIONAL, CLOUDFRONT]
MaintenanceMode:
Type: String
Default: 'OFF'
AllowedValues: ['ON', 'OFF']
Conditions:
IsMaintenanceMode: !Equals [!Ref MaintenanceMode, 'ON']
Resources:
WAFLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "aws-waf-logs-${EnvTagPrefix}-${ProjTagPrefix}"
RetentionInDays: 60
IPWhiteList:
Type: AWS::WAFv2::IPSet
Properties:
Name: !Sub ${EnvTagPrefix}-${ProjTagPrefix}-ip-whitelist
Description: Allowed IPs during maintenance
Scope: !Ref Scope
IPAddressVersion: IPV4
Addresses:
- 1.1.1.1/32
WebAcl:
Type: AWS::WAFv2::WebACL
Properties:
Name: !Sub ${EnvTagPrefix}-${ProjTagPrefix}-MaintenanceOnly
Scope: !Ref Scope
DefaultAction: { Allow: {} }
VisibilityConfig:
CloudWatchMetricsEnabled: true
SampledRequestsEnabled: true
MetricName: !Sub ${EnvTagPrefix}-${ProjTagPrefix}-maintenance
CustomResponseBodies:
MaintenanceHtml:
ContentType: TEXT_HTML
Content: |
<!doctype html><html lang="ja"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>メンテナンス中</title>
<style>
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;
font-family:system-ui,-apple-system,Segoe UI,Roboto,"Noto Sans JP",sans-serif;background:#0b1220;color:#e8eefc}
.card{max-width:720px;padding:24px;border-radius:14px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08)}
h1{margin:0 0 8px;font-size:20px}p{margin:8px 0 0;line-height:1.7;color:#bcd1f4}
</style></head>
<body><main class="card"><h1>🔧 メンテナンス中</h1>
<p>現在、システムメンテナンスを実施しております。しばらくしてから再度アクセスしてください。</p>
</main></body></html>
Rules:
- Name: !Sub ${EnvTagPrefix}-${ProjTagPrefix}-Allow-Whitelist
Priority: 0
Statement:
IPSetReferenceStatement:
Arn: !GetAtt IPWhiteList.Arn
Action: { Allow: {} }
VisibilityConfig:
CloudWatchMetricsEnabled: true
SampledRequestsEnabled: true
MetricName: !Sub ${EnvTagPrefix}-${ProjTagPrefix}-Allow-Whitelist
- !If
- IsMaintenanceMode
- Name: !Sub ${EnvTagPrefix}-${ProjTagPrefix}-Maintenance-Block
Priority: 1
Statement:
NotStatement:
Statement:
SizeConstraintStatement:
FieldToMatch: { SingleHeader: { Name: host } }
ComparisonOperator: LT
Size: 0
TextTransformations: [{ Priority: 0, Type: NONE }]
Action:
Block:
CustomResponse:
ResponseCode: 503
CustomResponseBodyKey: MaintenanceHtml
ResponseHeaders:
- { Name: Retry-After, Value: '3600' }
- { Name: Cache-Control, Value: 'no-cache, no-store, must-revalidate' }
VisibilityConfig:
CloudWatchMetricsEnabled: true
SampledRequestsEnabled: true
MetricName: !Sub ${EnvTagPrefix}-${ProjTagPrefix}-Maintenance-Block
- !Ref "AWS::NoValue"
WAFLogConfig:
Type: AWS::WAFv2::LoggingConfiguration
Properties:
LogDestinationConfigs:
- !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:${WAFLogGroup}"
ResourceArn: !GetAtt WebAcl.Arn
Outputs:
WebAclArn:
Value: !GetAtt WebAcl.Arn
WebAclId:
Value: !GetAtt WebAcl.Id
🔍 動作イメージ
| 状態 | 挙動 |
|---|---|
| MaintenanceMode = ON | Whitelist 以外は503でメンテナンスページ表示 |
| MaintenanceMode = OFF | 全アクセス許可(通常運用) |
メンテ切替はパラメータ 1 つの変更だけで即時反映されます。
WAF の設定は高速に反映されるため、実運用でも安心して使えます。
📷 サンプル画像
🧠 コード解説
✅ 常時許可ルール(Allow-Whitelist)
Statement:
IPSetReferenceStatement:
Arn: !GetAtt IPWhiteList.Arn
Action: { Allow: {} }
→ IPSet に登録された IP アドレスからのアクセスは常に許可します。
⚠️ メンテナンスブロックルール(Maintenance-Block)
NotStatement:
Statement:
SizeConstraintStatement:
FieldToMatch: { SingleHeader: { Name: host } }
ComparisonOperator: LT
Size: 0
→ 「host ヘッダのサイズが 0 未満」というあり得ない条件の否定、つまり常に一致します。
Whitelist で除外された全トラフィックがここにヒットし、503 を返します。
💬 カスタムレスポンス
ResponseCode: 503
CustomResponseBodyKey: MaintenanceHtml
ResponseHeaders:
- { Name: Retry-After, Value: '3600' }
- { Name: Cache-Control, Value: 'no-cache, no-store, must-revalidate' }
→ 1 時間後に再試行を促し、キャッシュを防止するヘッダーを付与しています。
🧾 注意点
- CloudFront 用の場合は us-east-1 で作成する必要があります。
- ALB や API Gateway など Regional WAF の場合は対象リージョンで OK。
- 503 のキャッシュを避けたい場合は、CloudFront のエラーページ TTL も短く設定しましょう。
- 監視やヘルスチェック用の IP は IPWhiteList に追加しておくと便利です。
🧰 応用アイデア
- 特定パスだけ許可(例:/status)を追加して監視ルートを残す
- 運用チームごとに IPSet を分離
- カスタム HTML をブランド仕様に差し替え(ロゴ・CSS 調整)
🚀 まとめ
| ポイント | 内容 |
|---|---|
| シンプルさ | ルール 2 つ+パラメータ1つで構成 |
| 安全性 | Whitelist で関係者だけ通す |
| 即時反映 | MaintenanceMode を切り替えるだけで即反映 |
| 拡張性 | CloudFront / ALB / API Gateway すべて対応 |
おわりに
このテンプレートはメンテナンスだけの内容ですが、すでに導入している AWS WAF があればそこへの組み込みも簡単に行えると思います。
📎 参考
