はじめに
「S3に画像を置いて、そのURLを直接叩けば表示できるのでは?」
AWSを使い始めたばかりのエンジニアなら、誰もが一度はこう考えるはずです。実際、Amazon S3(Simple Storage Service)に画像ファイルをアップロードして、公開設定にすれば、ブラウザからHTTPリクエストで画像を取得できます。技術的には、それだけで「画像配信」は成立します。
しかし、実務の現場では「S3単体での画像公開」はほぼ採用されません。代わりに、Amazon CloudFrontというCDN(Content Delivery Network)サービスをS3の前段に置く構成が、事実上の標準になっています。
この記事では、「なぜS3を直接公開するのではなく、CloudFront + S3の構成が推奨されるのか」を、初学者のエンジニアでも理解できるように、セキュリティ・パフォーマンス・コスト・運用の4つの観点から徹底的に解説します。
S3単体で画像を公開する仕組み
まず、S3だけで画像配信する場合の構成を確認しましょう。
基本的な流れ
ユーザー(ブラウザ)
↓ HTTPリクエスト
S3バケット(画像ファイル)
↓ レスポンス
ユーザー(画像表示)
S3バケットを作成し、画像をアップロードし、パブリックアクセスを許可すると、以下のようなURLで誰でも画像にアクセスできるようになります。
https://your-bucket.s3.ap-northeast-1.amazonaws.com/images/photo.jpg
HTMLでは以下のように記述するだけです。
<img src="https://your-bucket.s3.ap-northeast-1.amazonaws.com/images/photo.jpg" />
一見するとシンプルで便利に見えます。しかし、この構成にはいくつもの問題が潜んでいるのです。
問題1:セキュリティリスク — S3を公開するということの本当の意味
URLが知られれば誰でもアクセスできる
S3をパブリック公開するということは、URLを知っている人なら世界中の誰でもファイルにアクセスできるということです。
「URLを知らなければ安全では?」と思うかもしれません。しかし、これは大きな誤解です。
S3のバケット名やオブジェクトキーは、以下の方法で簡単に特定されます。
- 自動スキャンツール:インターネット上のS3バケットを自動的にスキャンするツールが多数存在します。バケット名は推測されやすく、一般的な命名規則に従っていると発見されるリスクが高まります
- 検索エンジンによるインデックス:設定次第では、GoogleなどにS3上のファイルがインデックスされてしまうことがあります
-
ファイル名の推測:
/images/product_001.jpgのような命名パターンなら、連番を変えるだけで他のファイルにもアクセスできてしまいます
「今は機密情報がない」は危険な思い込み
「画像しか入っていないから大丈夫」と考えるのは、実は最も危険な判断です。その理由は以下の通りです。
運用中にデータが混入するリスク
テスト用として公開設定にしたバケットに、後から本番データが入ってしまうケースは実際に多発しています。開発段階では画像だけだったバケットに、CSVファイルやログファイル、APIキーを含む設定ファイルなどが追加される可能性は常にあります。
設定ミスの連鎖
「読み取り専用で公開したつもりが、書き込み権限も付与されていた」という設定ミスは珍しくありません。書き込みが可能になると、攻撃者がマルウェアをアップロードしたり、既存のファイルを改ざんしたりする被害につながります。
AWS公式の推奨
AWSも公式ドキュメントで「S3バケットのパブリックアクセスは、必要な場合のみ許可すべき」と明記しています。デフォルトでパブリックアクセスブロックがONになっているのは、それだけリスクが高いからです。
CloudFrontならS3を隠せる
CloudFrontを使う場合、S3バケットは完全に非公開(プライベート)のまま運用できます。CloudFrontとS3の間はOAC(Origin Access Control)という仕組みで接続され、CloudFront経由のリクエストだけがS3にアクセスできるように制御されます。
ユーザー → CloudFront → S3(非公開)
↑
OACで認証
つまり、ユーザーがS3のURLを直接叩いても403エラーが返され、アクセスできません。ファイルはCloudFrontを経由した場合のみ取得できます。
これが「CloudFrontをS3の前に立てる」最大の理由です。
問題2:パフォーマンス — S3は「ストレージ」であって「配信サーバ」ではない
S3はデータの保管場所
S3はその名の通り「Simple Storage Service」、つまりストレージサービスです。データを安全に保存・管理するために設計されています。「高速にコンテンツを配信する」ことを主目的としたサービスではありません。
S3にHTTPリクエストを送ると、東京リージョン(ap-northeast-1)のデータセンターからファイルが返されます。日本国内のユーザーであればそれなりの速度が出ますが、毎回データセンターまでリクエストが往復するため、最適な速度とは言えません。
CloudFrontはCDN — 世界中にキャッシュを持つ配信ネットワーク
CloudFrontはCDN(Content Delivery Network)です。世界中に分散配置されたエッジロケーション(キャッシュサーバ)を持ち、ユーザーに最も近い場所からコンテンツを配信します。
具体的には以下のように動作します。
初回アクセス時:
ユーザー → CloudFront(エッジ)→ S3(オリジン)
キャッシュ保存 ←─── 画像取得
2回目以降:
ユーザー → CloudFront(エッジ)→ キャッシュから即返却
S3にはアクセスしない
この仕組みにより、以下のメリットが得られます。
- レスポンス速度の向上:ユーザーに近いエッジサーバから配信されるため、物理的な通信距離が短くなります。CDN経由の配信はS3直接配信より2〜3倍高速になるケースもあります
- S3への負荷軽減:キャッシュが効いている間はS3にリクエストが飛ばないため、S3の処理負荷が大幅に減ります
- 海外ユーザーへの対応:海外からのアクセスでも、各地域のエッジサーバから配信されるため、東京リージョンのS3から直接配信するよりはるかに高速です
画像配信はCDNと相性が抜群
画像ファイルは「一度アップロードしたら頻繁には変更しない」という特性があります。これはキャッシュとの相性が非常に良いことを意味します。
商品画像やアイコン、バナーなどの静的ファイルは、一度キャッシュされれば長期間そのまま使い回せます。結果として、S3へのアクセスはほぼ初回のみとなり、以降はすべてCloudFrontのキャッシュから高速配信されるのです。
問題3:コスト — 「S3の方が安い」は誤解
S3単体の課金構造
S3でファイルを公開配信する場合、以下の料金が発生します。
| 課金項目 | 内容 |
|---|---|
| ストレージ料金 | 保存しているデータ量に応じた課金 |
| GETリクエスト料金 | ファイルが取得されるたびに課金 |
| データ転送料金 | S3からインターネットへの転送量に応じた課金 |
この中で最も大きいのがデータ転送料金です。S3からインターネットへのデータ転送は約0.09ドル/GBかかります。
CloudFront + S3の課金構造
CloudFrontを使う場合の課金は以下のようになります。
| 課金項目 | 内容 |
|---|---|
| CloudFrontデータ転送料金 | ユーザーへの配信量に応じた課金 |
| CloudFrontリクエスト料金 | HTTPSリクエスト数に応じた課金 |
| S3 → CloudFront転送料金 | 無料 |
ここで重要なポイントが2つあります。
ポイント1:S3からCloudFrontへの転送は無料
S3から直接インターネットに配信すると転送料金がかかりますが、S3からCloudFrontへの転送は無料です。これだけでも大きなコストメリットがあります。
ポイント2:キャッシュによる転送量の削減
CloudFrontはキャッシュを活用するため、同じファイルへの2回目以降のアクセスではS3からデータを取得しません。つまり、実質的なデータ転送量が大幅に減少します。
具体的なシミュレーション
以下の条件で比較してみましょう。
- 画像サイズ:300KB/枚
- 月間アクセス数:10万回
- 合計転送量:約30GB
S3単体の場合:
データ転送:30GB × 約0.09ドル/GB = 約2.7ドル(約400円)
GETリクエスト:10万回 × 約0.004ドル/1万回 = 約0.04ドル
合計:約400円/月
CloudFront + S3の場合:
CloudFrontには月間1TBまでの無料枠があります(Always Free Tier)。30GBは余裕で無料枠内です。
合計:0円/月
この差は、アクセス数が増えるほど顕著になります。月間100万アクセス(転送量約300GB)でも、CloudFrontの無料枠(1TB)内に収まります。
「S3の方がシンプルだから安い」は間違い
S3単体はシンプルですが、キャッシュが存在しないため、すべてのリクエストで転送料金が発生します。一方、CloudFrontはキャッシュによって実質的な転送量を削減するため、全体コストが下がります。
特にDifyのようなチャットボットで画像を返す用途では、同じ商品画像が何度も表示されるケースが多いため、キャッシュの効果は絶大です。
問題4:運用リスク — S3公開は「時限爆弾」になりうる
コスト爆発のリスク
S3を直接公開していると、誰でもアクセスできるため、ボットや悪意ある第三者が大量のリクエストを送信するリスクがあります。
通常は月数百円で済んでいた転送料金が、ある日突然数万円に跳ね上がるという事例は、AWSユーザーのコミュニティでも度々報告されています。
CloudFrontにはレートリミットやWAF(Web Application Firewall)との連携機能があり、異常なアクセスに対する防御が可能です。また、AWS Shield Standardによる基本的なDDoS保護が標準で含まれています。
URL管理の問題
S3のURLを直接公開すると、以下のような問題が発生します。
- 一度公開したURLは制御不能:外部サイトに埋め込まれたり、SNSで拡散されたりすると、ファイルを削除してもキャッシュやリンクが残り続けます
- バケット構造がバレる:URLにはバケット名やファイルパスが含まれるため、内部のディレクトリ構造が外部から推測されます
- URL変更が困難:S3のURLはAWSのドメイン名やリージョン名を含むため、将来的にバケットを移動したり、リージョンを変更したりすると、すべてのURLが無効になります
CloudFrontを使えば、独自ドメイン(例:cdn.example.com)でURLを統一でき、バックエンドのS3バケットを自由に変更しても、ユーザーに見えるURLは変わりません。
将来の拡張性
最初は画像配信だけでも、サービスが成長すると以下のようなニーズが出てきます。
- 画像の動的リサイズ(Lambda@Edge)
- アクセス制限(署名付きURL)
- 地域ごとのアクセス制御
- HTTPS + 独自ドメイン
S3単体ではこれらの要件に対応できませんが、CloudFrontならすべて実現可能です。最初からCloudFrontを導入しておけば、後から大幅なアーキテクチャ変更をせずに機能を拡張できます。
CloudFront + S3の構成を実際に構築する
ここからは、実際にCloudFront + S3の構成を構築する手順を解説します。
STEP 1:S3バケットの作成(非公開のまま)
AWSマネジメントコンソールからS3バケットを作成します。重要な設定は以下の通りです。
| 設定項目 | 値 |
|---|---|
| バケットタイプ | 汎用 |
| リージョン | ap-northeast-1(東京) |
| オブジェクト所有者 | ACL無効(推奨) |
| パブリックアクセスブロック | すべてON(ブロック) |
| バージョニング | 無効 |
| 暗号化 | SSE-S3(デフォルト) |
ここで最も重要なのは、パブリックアクセスブロックをONのままにすることです。S3は非公開のまま作成します。画像のアップロードだけ行い、公開設定には一切触りません。
STEP 2:CloudFrontディストリビューションの作成
CloudFrontのコンソールから「ディストリビューションを作成」を選択します。
Origin設定(最重要):
| 設定項目 | 値 |
|---|---|
| Origin domain | 作成したS3バケットを選択 |
| Origin path | 空欄 |
| Origin access | Origin Access Control(OAC) |
OACの設定で「Create new OAC」を選択し、「Sign requests(推奨)」を有効にします。これにより、CloudFrontがS3に対して署名付きリクエストを送信し、認証されたアクセスのみが許可される仕組みになります。
その他の設定:
| 設定項目 | 推奨値 |
|---|---|
| Price Class | Price Class 100(日本中心なら十分) |
| Viewer protocol | Redirect HTTP to HTTPS |
| WAF | 画像配信のみなら不要 |
| キャッシュ設定 | 推奨設定のまま |
STEP 3:S3バケットポリシーの設定
CloudFrontディストリビューション作成後、S3に対してバケットポリシーを追加する必要があります。CloudFrontが自動生成するポリシーをコピーし、S3の「アクセス許可」タブ内のバケットポリシーに貼り付けます。
ポリシーの内容は以下のような形式です。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EDFDVBD6EXAMPLE"
}
}
}
]
}
このポリシーにより、指定されたCloudFrontディストリビューションからのリクエストのみがS3へのアクセスを許可されます。
STEP 4:動作確認
CloudFrontのデプロイが完了(Status: Deployed)したら、以下のURLで画像にアクセスできるか確認します。
https://dxxxxx.cloudfront.net/images/photo.jpg
ブラウザで画像が表示されれば、構築は成功です。
確認すべき動作:
- CloudFront URL → 画像が表示される(正常)
- S3 URL直接 → 403 Forbidden(正常:アクセスがブロックされている)
この2つが確認できれば、セキュアな画像配信基盤が完成しています。
Difyとの連携:チャットボットで画像を表示する
CloudFront + S3の基盤ができたら、Difyのようなチャットボットプラットフォームと連携して、回答に画像を含める実装が可能になります。
基本的なアプローチ
DifyのLLMは「画像そのもの」を返すことはできません。代わりに、画像のURLをテキストとして返し、フロントエンド側で画像としてレンダリングします。
ユーザーの質問
↓
Dify(LLM + RAG)
↓
CloudFrontのURLを含むテキスト回答
↓
フロントエンドで画像表示
Markdown形式で画像を返す
Difyの回答UIはMarkdownをレンダリングできるため、Markdown形式で画像を記述すると、チャット画面上に画像が直接表示されます。

プロンプトには以下のように指示を含めます。
画像URLが取得できた場合は、必ずMarkdown形式で出力してください。
例:
RAGのナレッジに画像URLを含める
スプレッドシートやCSVでナレッジデータを管理している場合、image_urlカラムを追加してCloudFrontのURLを保持します。
商品名,説明,カテゴリ,image_url
車椅子A,軽量で持ち運びやすい,移動補助,https://dxxxxx.cloudfront.net/images/wheelchair_a.jpg
このデータをDifyのDatasetに取り込む際、image_urlがチャンク分割で失われないよう、メタデータとして保持する設計が重要です。
CloudFrontの料金は実際どのくらいかかるのか
「CloudFrontを導入するとコストが増えるのでは?」という不安があるかもしれません。実際の料金感を確認しましょう。
無料枠
CloudFrontには常時無料枠(Always Free Tier)があり、月間1TBのデータ転送と1,000万回のHTTPSリクエストが無料で利用できます。
具体的なコスト感
| ユースケース | 月間アクセス | 転送量 | 月額目安 |
|---|---|---|---|
| 個人開発・PoC | 〜1万回 | 〜1GB | 0円 |
| 社内ツール | 〜10万回 | 〜30GB | 0円 |
| 小規模サービス | 〜100万回 | 〜300GB | 0円 |
| 中規模サービス | 〜1,000万回 | 〜3TB | 数千円 |
画像配信用途の場合、画像サイズが数百KB程度であれば、かなりのアクセス数でも無料枠内に収まります。
コスト最適化のポイント
- Price Class 100を選択する:日本を含むアジア・北米・ヨーロッパのエッジロケーションのみを使う設定で、料金が抑えられます
- 画像の最適化:WebP形式への変換や圧縮により、ファイルサイズを削減すると転送コストが下がります
-
キャッシュTTLの調整:
Cache-Control: max-age=86400のようにキャッシュ有効期間を長く設定することで、オリジン(S3)へのリクエストを最小化できます
まとめ:S3 + CloudFrontは「標準構成」である
ここまで4つの観点からS3単体とCloudFront + S3を比較してきました。
| 観点 | S3単体 | CloudFront + S3 |
|---|---|---|
| セキュリティ | バケットを公開する必要あり | S3は非公開のまま運用可能 |
| パフォーマンス | 毎回オリジンから配信 | エッジキャッシュで高速配信 |
| コスト | 全リクエストに転送料発生 | キャッシュで転送量削減、無料枠あり |
| 運用 | URL管理・拡張が困難 | 独自ドメイン・機能拡張が容易 |
S3は優れたストレージサービスですが、その役割は「データの保管」です。コンテンツをユーザーに「配信」するのはCloudFrontの役割です。この2つのサービスがそれぞれの得意分野を担うことで、安全で高速、かつコスト効率の良い画像配信基盤が実現します。
特にDifyのようなAIチャットボットと連携する場合、同じ画像が繰り返し表示されるケースが多いため、CloudFrontのキャッシュが非常に有効に機能します。
「とりあえずS3で動くから」ではなく、最初からCloudFront + S3の構成を選択することが、後々の手戻りを防ぎ、プロダクション品質のアーキテクチャへの近道となるのです。
参考:構成の全体像
最終的な構成は以下のようになります。
ユーザー
↓ HTTPS
CloudFront(CDN / エッジキャッシュ)
↓ OAC認証
S3バケット(非公開 / 画像保管)
Dify(チャットボット)
↓ CloudFrontのURLを含む回答
フロントエンド
↓ <img src="https://xxx.cloudfront.net/images/photo.jpg" />
ユーザーの画面に画像表示
この構成がAWSにおける画像配信の「標準アーキテクチャ」であり、個人開発からエンタープライズまで幅広く採用されている実績のある設計パターンです。