WordPressのブログを静的化してCloudFront+S3に移行してみた

WordPressのブログを静的化してCloudFront+S3に移行してみた

Clock Icon2025.07.23

こんにちは、ゲームソリューション部のsoraです。
今回は、WordPressで運営していたブログを静的化してCloudFront+S3に移行したことを書いていきます。

はじめに

使用環境

  • ドメインはRoute53で管理中(移管済み)
  • ConoHaのレンタルサーバ上でWordPressが動作中
  • 利用ドメインは3つ
  • 3ドメインでの合計PV数は1日200PVと仮定

コスト比較

移行を行う前に、簡単にコスト比較をしました。
今回の移行元のConoHa WINGパックについては、以下をご確認ください。
https://www.conoha.jp/wing/wingpack/

ConoHaで利用しているプランや支払方法(何か月分の前払いか)、サイトのPV数によって変わるため、あくまで今回のケースにおける比較であることをご理解ください。
また、各料金はタイミングによって変動する可能性があることをご了承ください。

項目 ConoHa:WINGパックベーシック(現在) AWS:CloudFront+S3+Route53(移行後)
サーバ基本料金 11,292円/年(12か月払い) 0円
ドメイン料金 2,816円/年(無料ドメイン1つ+有料2つ) $45(≒6600円)/年(Route53 ドメイン3つ)
DNS管理料金 なし(込みの料金のため) $18(≒2,700円)/年(Route53 ホストゾーン3つ)
DNSクエリ料金 なし(込みの料金のため) $0.06(≒9円)/年(200PV/日想定)
ストレージ料金 なし(込みの料金のため) 約360円/年(S3ストレージ)
CDN料金 なし 0円(CloudFront無料枠内)
データ転送料金 なし(込みの料金のため) 0円(無料枠内、200PV/日想定)
年間合計 13,508円 9,669円
月額換算 941円 806円
年間差額 ▲3,839円(節約)

Route53で無料ドメインがなかったりそもそものドメイン料金が高かったりするものの、静的化することによりサーバが不要になり、今回の用途においてはトータルで見るとAWSの方が安くなりました。
また、私がAWSに慣れていることもあり、AWSで管理した方が運用負荷がかからないため、AWSへ移行することにしました。

事前準備

今回、WordPressで運用しているサイトを静的化するにあたって、動的な処理が必要なものは利用できなくなります。
主に私が確認したのは以下です。

  • お問い合わせ機能
  • サイト内検索機能
  • Amazonアフィリエイトなどでのリアルタイム価格表示
    • LambdaなどでAPIを実行して定期的に価格取得することで対処可能ですが、CloudFront+S3のみでは実現が難しいです。
    • 私は価格表示をしないことにしました。
  • 記事の週間ランキング機能
    • 動的に変更されないため、そのまま持っていくと表示されているランキングで固定されることになります。

また、WordPressにて利用しているテーマについて、ライセンス周りの制限があれば他のテーマに切り替えるなど対応が必要となります。
Googleアドセンスについては、JavaScriptの記述だけであり静的化しても特に影響はありませんでした。

(WordPress)静的サイトの資材生成

WordPressの管理画面でプラグインを使用してサイトの静的化を行います。
Simply Staticというプラグインを使用して静的サイトを生成します。
sr-wordpress-static-s3-01

プラグイン設定では以下にチェックを入れて実行します。

  • 404ページの自動生成
  • 管理者画面周りのファイルは対象外
  • テーマや画像などのコンテンツも対象

sr-wordpress-static-s3-02

上記設定を入れた後に実行すると、WordPressサイトの全コンテンツが静的ファイルとして出力されます。

sr-wordpress-static-s3-03

(AWS)必要リソースの作成・ファイルアップロード

次に、AWSで必要なリソース(主にCloudFront・S3)を作成します。
今回はTerraformで作成しました。
以下にコードを記載しますが、provider.tfvariable.tfなどは割愛しています。

Route53のホストゾーンへのAレコードの追加やACMの証明書作成などは手動で行うため、Terraformでは作成しません。
CloudFrontのログは出力する設定にしているものの、コスト削減のために90日で削除するようにしています。

Terraformコード(main.tf)
main.tf
# 静的サイト用S3バケット
resource "aws_s3_bucket" "static_site" {
  bucket = "${var.project_name}-${var.environment}-static-site"
  tags = {
    Name        = "${var.project_name}-${var.environment}-static-site"
    Environment = var.environment
    Project     = var.project_name
  }
}

# CloudFrontログ用S3バケット
resource "aws_s3_bucket" "cloudfront_logs" {
  bucket = "${var.project_name}-${var.environment}-cloudfront-logs"
  tags = {
    Name        = "${var.project_name}-${var.environment}-cloudfront-logs"
    Environment = var.environment
    Project     = var.project_name
  }
}

# CloudFrontログ用S3バケットのACL設定
resource "aws_s3_bucket_ownership_controls" "cloudfront_logs" {
  bucket = aws_s3_bucket.cloudfront_logs.id
  rule {
    object_ownership = "BucketOwnerPreferred"
  }
}

resource "aws_s3_bucket_acl" "cloudfront_logs" {
  depends_on = [aws_s3_bucket_ownership_controls.cloudfront_logs]
  bucket = aws_s3_bucket.cloudfront_logs.id
  acl    = "private"
}

# ログの自動削除設定(コスト削減のため)
resource "aws_s3_bucket_lifecycle_configuration" "cloudfront_logs" {
  bucket = aws_s3_bucket.cloudfront_logs.id
  rule {
    id     = "delete_old_logs"
    status = "Enabled"
    filter {
      # 空のfilterは全てのオブジェクトに適用
    }
    expiration {
      days = 90  # 90日後に自動削除
    }
  }
}

resource "aws_cloudfront_origin_access_control" "static_site" {
  name                              = "${var.project_name}-${var.environment}-oac"
  description                      = "OAC for ${var.project_name} static site"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

resource "aws_cloudfront_function" "uri_rewrite" {
  name    = "${var.project_name}-${var.environment}-uri-rewrite"
  runtime = "cloudfront-js-2.0"
  comment = "URI rewrite function for clean URLs"
  publish = true
  code    = file("${path.module}/functions/uri-rewrite.js")
}

resource "aws_cloudfront_distribution" "static_site" {
  origin {
    domain_name              = aws_s3_bucket.static_site.bucket_regional_domain_name
    origin_id                = "S3-${aws_s3_bucket.static_site.bucket}"
    origin_access_control_id = aws_cloudfront_origin_access_control.static_site.id
  }

  enabled             = true
  is_ipv6_enabled     = true
  comment             = "${var.project_name} static site distribution"
  default_root_object = "index.html"

  # ドメイン設定(後ほど設定予定)
  # aliases = [var.domain_name]

  # CloudFrontログ設定
  logging_config {
    include_cookies = false
    bucket         = aws_s3_bucket.cloudfront_logs.bucket_domain_name
    prefix         = "access-logs/"
  }

  default_cache_behavior {
    allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-${aws_s3_bucket.static_site.bucket}"

    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
    compress               = true

    function_association {
      event_type   = "viewer-request"
      function_arn = aws_cloudfront_function.uri_rewrite.arn
    }
  }

  # 404エラー時に用意された404ページを表示
  custom_error_response {
    error_code            = 404
    response_code         = 404
    response_page_path    = "/404/index.html"
    error_caching_min_ttl = 0
  }

  # 403エラー時も404ページを表示(S3のアクセス拒否時)
  custom_error_response {
    error_code            = 403
    response_code         = 404
    response_page_path    = "/404/index.html"
    error_caching_min_ttl = 0
  }

  price_class = "PriceClass_200"

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    # デフォルト証明書を使用(ACMは後ほど設定予定)
    cloudfront_default_certificate = true
    # ACMの証明書設定(後ほど有効化)
    # acm_certificate_arn      = var.acm_certificate_arn
    # ssl_support_method       = "sni-only"
    # minimum_protocol_version = "TLSv1.2_2021"
    # cloudfront_default_certificate = false
  }

  tags = {
    Name        = "${var.project_name}-${var.environment}-distribution"
    Environment = var.environment
    Project     = var.project_name
  }
}

resource "aws_s3_bucket_policy" "static_site" {
  bucket = aws_s3_bucket.static_site.id
  policy = templatefile("${path.module}/policy/s3-cloudfront-policy.json", {
    s3_bucket_arn             = aws_s3_bucket.static_site.arn
    cloudfront_distribution_arn = aws_cloudfront_distribution.static_site.arn
  })
}

リソースが作成できたら、Terraformで作成したS3にWordPressで静的化したファイルを配置します。
最初にアップロードの残り時間が6時間と表示されて驚きましたが、1,2時間程度でアップロードできました。

sr-wordpress-static-s3-04

Aレコードの向き先を変更する前に、移行前の動作確認としてCloudFrontのディストリビューションドメイン名で接続して、表示に問題がないことも確認しました。
(この段階でGoogleアドセンスは表示されません。)

(AWS)ACMの証明書作成とRoute53設定

ACMの証明書の作成とCloudFrontのCNAME設定、CloudFrontの証明書の設定変更、Aレコードの向き先変更を行います。
こちらはよくある手順のため、説明を割愛させていただきます。

Route53でホストゾーンのAレコードをCloudFrontに変更することで、名前解決の向き先が変わり、CloudFrontへアクセスが来ることになります。

動作確認

設定完了後、DNSの変更が正しく反映されているか確認します(即座に反映されるわけではありません)。

Aレコードの確認

$ dig A {ドメイン名}

;; ANSWER SECTION:
{ドメイン名}.     60      IN      A       18.xxx.xx.xx
{ドメイン名}.     60      IN      A       18.xxx.xx.xx
{ドメイン名}.     60      IN      A       18.xxx.xx.xx
{ドメイン名}.     60      IN      A       18.xxx.xx.xx

IPアドレスが18.xxx.xx.xxになっており、元々指定していたサーバからCloudFrontに切り替わっていることが確認できました。

HTTP応答の確認

$ curl -I https://{ドメイン名}
HTTP/2 200
content-type: text/html
content-length: 330487
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 xxxxxxxxxxxxxx.cloudfront.net (CloudFront)

server: AmazonS3via: 1.1 xxxxxxxxxxxxxx.cloudfront.net (CloudFront)により、S3からCloudFront経由で配信されていることが確認できました。

(補足)Googleアドセンス広告の動作について

ドメイン移行直後、Googleアドセンス広告は表示されない場合がありますが、一定時間経過後に正常に表示されるようになりました。
この表示までのラグについて、Googleアドセンスが新しいドメインの設定を認識し、広告配信の承認を行うまでに時間がかかることが理由です。

最後に

今回は、WordPressのブログを静的化してCloudFront+S3に移行したことを記事にしました。
サイトの静的化により、コスト削減や高いパフォーマンスを実現できました。
どなたかの参考になると幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.