2026年5月23日

2026年5月23日

Nginx で WordPress を動かす設定(server ブロックの書き方)|PHP-FPM 連携・パーマリンクの 404・FastCGI キャッシュ

Nginx で WordPress を動かすときにつまずきやすいのは、パーマリンクの 404、PHP-FPM との接続、location の書き順によるセキュリティ設定の取りこぼしの3つです。この記事では、Nginx と PHP-FPM で WordPress を動かす server ブロックを一から書き、動作確認と FastCGI キャッシュの導入までを説明します。

例は 2026年10月時点の Ubuntu 24.04 LTS(標準パッケージは Nginx 1.24、PHP 8.3)です。ドメインは example.com、WordPress の設置先は /var/www/html とします。別の OS やバージョンでは、パッケージ名とソケットのパスを読み替えてください。

よくある症状と原因

症状 主な原因
投稿ページなどが 404 になる(トップページは表示される) location / に try_files が無く、存在しない URL が index.php に渡らない
502 Bad Gateway fastcgi_pass のソケットのパスが PHP-FPM の実際のパスと違う、PHP-FPM が起動していない
PHP ファイルがダウンロードされる .php を PHP-FPM に渡す location が無い
uploads に置かれた PHP が実行できてしまう 拒否する location が、PHP を処理する location より後に書かれている

Nginx には Apache の .htaccess に当たる仕組みがありません。WordPress の管理画面でパーマリンクを保存しても Nginx の設定は変わらないので、リライトは server ブロックの try_files で行います。

手順1:PHP-FPM と必要な拡張を入れる

sudo apt update
sudo apt install nginx php-fpm php-mysql php-curl php-gd php-intl \
    php-mbstring php-xml php-zip php-imagick

# PHP-FPM のソケットのパスを確認する
ls /run/php/
# 例: php8.3-fpm.sock  ← このパスを fastcgi_pass に書く

# PHP-FPM の起動と自動起動
sudo systemctl enable --now php8.3-fpm

php-fpm などの名前で入れると、その OS の標準バージョン(Ubuntu 24.04 では 8.3)が入ります。ソケットのファイル名はバージョンを含むので、ls /run/php/ で実際の名前を確かめてから設定に書きます。

ファイルの所有者は、PHP-FPM の実行ユーザー(Ubuntu の既定は www-data)にします。

sudo chown -R www-data:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;

この所有者にすると、管理画面からのプラグイン・テーマの更新が FTP 情報を聞かれずに動きます。一方で Web サーバーのプロセスがすべてのファイルを書き換えられる状態になるので、更新を WP-CLI などで行う運用なら、書き込みを wp-content 配下に限る方法もあります。

手順2:WordPress 用の server ブロックを書く

/etc/nginx/sites-available/wordpress.conf を作ります。証明書は Let's Encrypt で取得済みの前提です(取得前に試すなら、まず 80 番の server だけで動かします)。

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # HTTP は HTTPS へ転送する
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    root /var/www/html;
    index index.php index.html;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_session_cache   shared:SSL:10m;

    client_max_body_size 64m;

    # パーマリンク: ファイルもディレクトリも無ければ index.php に渡す
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    # ドットで始まるファイル(.git、.htaccess など)を拒否する
    # Let's Encrypt の更新で使う /.well-known/ は除く
    location ~ /\.(?!well-known/) {
        deny all;
    }

    location = /wp-config.php {
        deny all;
    }

    # uploads 内の PHP は実行させない(PHP の location より前に書く)
    location ~* /(?:uploads|files)/.*\.php$ {
        deny all;
    }

    # PHP-FPM に渡す
    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_read_timeout 300;
    }

    # 静的ファイルはブラウザにキャッシュさせ、無い場合のログを出さない
    location ~* \.(?:css|js|jpe?g|png|gif|webp|avif|ico|svg|woff2?)$ {
        expires 30d;
        access_log off;
        log_not_found off;
    }
}

設定の要点は次のとおりです。

  • location の書き順:正規表現の location(~ と ~*)は、設定ファイルに書いた順に照合され、最初に一致したものが使われます。uploads の PHP を拒否する location を location ~ \.php$ より後に書くと、先に PHP の location に一致して実行されてしまいます。拒否する location は PHP の location より前に置きます。
  • try_files:$uri $uri/ /index.php?$args で、実在するファイルはそのまま返し、それ以外は index.php にクエリ文字列ごと渡します。これが .htaccess のリライトの代わりです。
  • PHP の location の try_files:try_files $uri =404; は、存在しない .php へのリクエストを PHP-FPM に渡さず 404 にします。
  • ドットファイルの拒否:単純に location ~ /\. と書くと /.well-known/acme-challenge/ も拒否され、Let's Encrypt の HTTP 認証による証明書更新が失敗します。上の例では .well-known/ を除いています。
  • http2:Ubuntu 24.04 の Nginx 1.24 では listen 443 ssl http2; と書きます。Nginx 1.25.1 以降では listen 443 ssl; と別行の http2 on; が新しい書き方で、古い書き方は警告が出ます。nginx -v で確かめてください。
  • 静的ファイルの期限:expires を長くするほど、ファイルを差し替えたときにブラウザに古いものが残ります。WordPress はテーマやプラグインの CSS・JS に ?ver= を付けて出力するので更新は反映されますが、同じ URL のまま画像を差し替える運用なら短めにします。

手順3:設定を有効にして確認する

# 設定を有効にする
sudo ln -s /etc/nginx/sites-available/wordpress.conf /etc/nginx/sites-enabled/

# 既定のサイトが 80 番を使っている場合は外す
sudo rm /etc/nginx/sites-enabled/default

# 文法を確認してから反映する
sudo nginx -t
sudo systemctl reload nginx

nginx -t で「syntax is ok」「test is successful」と出てから reload します。続けて、応答を確認します。

# HTTP は 301 で HTTPS に転送される
curl -sI http://example.com/ | head -n 3

# パーマリンクの URL が 200 になる(実在する投稿の URL で試す)
curl -sI https://example.com/sample-post/ | head -n 1

# 未ログインで管理画面を開くと 302 でログイン画面に転送される
curl -sI https://example.com/wp-admin/ | grep -i -E "^HTTP|^location"

# uploads に置いた PHP が実行されず 403 になる
echo '<?php echo "ng";' | sudo tee /var/www/html/wp-content/uploads/test.php > /dev/null
curl -sI https://example.com/wp-content/uploads/test.php | head -n 1
sudo rm /var/www/html/wp-content/uploads/test.php

uploads のテストで 200 が返り本文に ng と出る場合は、手順2の location の順番を見直してください。

手順4:WordPress 側の設定

パーマリンクは管理画面の「設定」→「パーマリンク」で選んで保存します。Nginx で動いていることを WordPress は自動で判別するので、URL に index.php は付きません。WP-CLI なら次のように設定できます。

cd /var/www/html
sudo -u www-data wp rewrite structure '/%postname%/'
sudo -u www-data wp option get permalink_structure

wp rewrite structure はパーマリンクの形式を保存し、WordPress 内部のリライトルールを作り直します。Nginx では .htaccess を使わないので、.htaccess を書き出す --hard は付けなくてかまいません。

サイトの URL は、インストール時に https://example.com で設定していれば変更は不要です。HTTP で入れた場合は「設定」→「一般」の「WordPress アドレス (URL)」と「サイトアドレス (URL)」を https:// に変えます。この構成では Nginx 自身が HTTPS を受けていて fastcgi_params が PHP に HTTPS を伝えるので、ロードバランサーの後ろに置くときに使う X-Forwarded-Proto の判定を wp-config.php に書く必要はありません。

問題が起きたときは、Nginx と PHP-FPM のエラーログを見ます。

sudo tail -n 50 /var/log/nginx/error.log
sudo journalctl -u php8.3-fpm -n 50

手順5:FastCGI キャッシュで高速化する

FastCGI キャッシュは、PHP が生成したページを Nginx がファイルに保存し、次からは PHP を通さずに返す仕組みです。ログイン中のユーザーやコメント投稿者に他人向けのページを返さないよう、除外条件を合わせて設定します。

まず http コンテキストに、キャッシュの保存先とキーを定義します。Ubuntu では /etc/nginx/conf.d/ 内のファイルが http ブロックの中で読み込まれるので、/etc/nginx/conf.d/fastcgi-cache.conf として保存します。

fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=WORDPRESS:100m
                   inactive=60m max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

次に、手順2の HTTPS の server ブロックにキャッシュしない条件を追加し、PHP の location をキャッシュ付きのものに置き換えます。

    # キャッシュしない条件(server ブロック内、location の外)
    set $skip_cache 0;

    if ($request_method = POST) {
        set $skip_cache 1;
    }
    if ($query_string != "") {
        set $skip_cache 1;
    }
    if ($request_uri ~* "/wp-admin/|/wp-json/|/xmlrpc.php|wp-.*\.php|/feed/|sitemap") {
        set $skip_cache 1;
    }
    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|woocommerce_items_in_cart") {
        set $skip_cache 1;
    }

    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_read_timeout 300;

        fastcgi_cache WORDPRESS;
        fastcgi_cache_valid 200 60m;
        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache $skip_cache;
        add_header X-FastCGI-Cache $upstream_cache_status;
    }
  • fastcgi_cache_valid は 200 だけにしています。リダイレクト(301・302)をキャッシュすると、ログイン後の転送先などが他の人に返ることがあるためです。
  • Cookie の条件で、ログイン中のユーザー、コメント投稿者、パスワード保護ページを見た人、WooCommerce でカートに商品を入れた人をキャッシュの対象外にしています。会員サイトやネットショップでは、使っているプラグインの Cookie とページ(カート、決済、マイページ)も除外に加えてください。
  • オープンソース版の Nginx には、キャッシュを URL 単位で消すコマンドがありません。記事を更新しても、最大で fastcgi_cache_valid の時間は古いページが返ります。更新をすぐ反映したい場合は、キャッシュの削除に対応したプラグイン(例:Nginx Helper)を使うか、保存先のファイルを消します。

設定を反映して、キャッシュが効いているかを確認します。

sudo mkdir -p /var/cache/nginx/wordpress
sudo nginx -t && sudo systemctl reload nginx

# 1回目は MISS、2回目は HIT になる
curl -sI https://example.com/ | grep -i x-fastcgi-cache
curl -sI https://example.com/ | grep -i x-fastcgi-cache

# キャッシュをすべて消す
sudo find /var/cache/nginx/wordpress -type f -delete

ログインした状態のブラウザでは BYPASS になるのが正しい動きです。

まとめ

  • パーマリンクは location / の try_files $uri $uri/ /index.php?$args; で通します。管理画面での保存は Nginx の設定を変えません。
  • fastcgi_pass のソケットのパスは ls /run/php/ で確かめた実際の名前を書きます。
  • 正規表現の location は書いた順に照合されるので、uploads の PHP を拒否する location は PHP の location より前に置き、curl で 403 になることを確かめます。
  • ドットファイルの拒否から /.well-known/ を除き、証明書の更新を止めないようにします。
  • FastCGI キャッシュは 200 の応答だけを保存し、POST、クエリ文字列、管理画面、ログイン Cookie などを除外します。

関連記事:

お気軽にご相談ください

お見積りへ お問い合わせへ
▲