Node.jsアプリを生成AIで作ったら、HTTPアクセスで困った話
こんにちは。テクニカルサポートチームのShiinaです。
はじめに
最近では、Claude Code などの生成 AI によるコーディング支援ツールのおかげで、Web アプリを手軽に作れるようになってきました。
アプリケーションの機能や特徴をわかりやすく伝えるために、プロトタイプを作って試したい、という場面も多いですよね。
そんなとき、プロトタイプはセットアップが簡単な HTTP でサクッと動かしたいと思いませんか?
Node.js の Web アプリを EC2 にデプロイし、HTTP アクセスで動かそうとしたところ、思わぬトラブルにハマってしまいました。
今回はそのときの経験をまとめました。似たようなことでお困りの方のヒントになれば幸いです。
困っていたこと
Node.js の Express アプリケーションを EC2 インスタンスにデプロイしたのですが、HTTP アクセスしようとすると読み込みがタイムアウトしてしまい、正しく動作しませんでした。
具体的な事象
- CSS や JavaScript が "ERR_ADDRESS_UNREACHABLE" エラーで読み込まれない
- アプリケーション側にはログやエラーメッセージが記録されていない
- ローカルホストで動作させた場合には問題が発生しない
結論
HTTP レスポンスヘッダーStrict-Transport-Security
およびContent-Security-Policy
による、強制的な HTTPS アクセス試行が原因でした。
Express アプリケーションのセキュリティを強化するために利用していたパッケージ「Helmet」により、以下のヘッダーが自動的に設定されていました。
Strict-Transport-Security ヘッダ
デフォルトでStrict-Transport-Security
ヘッダーが設定されます。
Strict-Transport-Security: max-age=15552000; includeSubDomains
Content-Security-Policy ヘッダ
デフォルトでContent-Security-Policy
ヘッダにupgrade-insecure-requests
ディレクティブが設定されます。
Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
Strict-Transport-Security とは
ウェブサイトが安全な HTTPS 通信だけを使うようにブラウザに指示する仕組みです。
これを設定すると、ユーザーが間違って HTTP でアクセスしても自動的に HTTPS に切り替わります。
Content-Security-Policy とは
ウェブサイトのセキュリティを高めるための設定です。
リソース(画像やスクリプトなど)へのアクセスを、自動的に「https://」に書き換えて安全に読み込むように指示できます。
これにより、混在コンテンツ(HTTPS ページ内に HTTP リソースがある状態)を防ぎ、通信の安全性を保てます。
ヘッダーを確認してみる
事象が発生した際のレスポンスヘッダーに Strict-Transport-Security
およびContent-Security-Policy
が設定されているか確認してみました。
開発ツール
ブラウザの開発者ツールでネットワークアクティビティを確認することで、ヘッダーの設定を確認できます。
curl コマンド
curl コマンドの -I
オプションを利用することで、レスポンスヘッダーを確認できます。
curl -I http://<IPアドレス>/
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Tue, 08 Jul 2025 07:16:53 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 4564
Connection: keep-alive
Vary: Accept-Encoding
Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Origin-Agent-Cluster: ?1
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Frame-Options: DENY
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 0
X-XSS-Protection: 1; mode=block
Access-Control-Allow-Origin: *
ETag: W/"11d4-d+p88cyMAjFJXU46aRWJM+DRhwI"
Set-Cookie: connect.sid=s%3AlDU2OwmQoHE1nNNt_9JmhlOqbMoI5YqF.ur3m%2F41Wd9IG4XiUsQ0wWFAGcZcGAysI%2F9C0ZtCQ2TI; Path=/; Expires=Wed, 09 Jul 2025 07:16:53 GMT; HttpOnly
Cf-Team: 288a7e20290000d518c0532400000001
Helmets の設定変更する
一時的なデモやプロトタイプ用途であれば、ヘッダーを無効化することで HTTP でも動作させることができます。
app.js
の Helmet 設定でStrict-Transport-Security
およびContent-Security-Policy
を無効化します。
app.use(helmet({
strictTransportSecurity: false,
contentSecurityPolicy: false
}));
設定を変更した後は、アプリケーションを再起動してください。
また、ブラウザにキャッシュが残っている場合があるため、一度ブラウザのキャッシュをクリアしてから動作を確認してください。
無事に HTTP アクセスできるようなりました。
まとめ
今回のトラブルは、最初はセキュリティグループやファイアウォール、リバースプロキシなどネットワーク周りの設定ミスを疑っていましたが、実際は HTTP ヘッダーの設定が原因という、罠でした。
生成 AI の進化によってアプリケーションやコンテンツ自体は簡単に作れるようになっていますが、
実際に動かすとなると、やはりフレームワークやミドルウェアの仕組み、セキュリティ設定などの基礎知識が重要だと改めて実感しました!
本記事が同じようなトラブルで悩んでいる方の参考になれば幸いです。
参考