サーバーがPDFを扱えるか測る|PHPファイル1つで確かめる方法【コード付き】

サーバーにPHPファイルを1つ置いてPDFの設定を測る様子を示したイメージ図 ITライフハック
この記事は約32分で読めます。
当ページはプロモーションが含まれています

前回、共用サーバーがPDFを禁止しているのかを実際に測った話を書きました。

結果は、エックスサーバーは許可していたというものでした。通説とは違いました。

今回は、その測り方です。

WordPressもプラグインも使いません。 PHPファイルを1つ置いて、開いて、消す。それだけです。

プローブを設置して開いて削除するまでの3ステップを示した図

なぜ、専用のものが要るのか

サイトヘルスでは、そこまで分かりません。

WordPress の「ツール」→「サイトヘルス」→「情報」→「メディア処理」で、ImageMagick が入っているかと、その版までは分かります。

分からないのは、その先です。

・PDF の読み込みが許可されているか
・禁止されているなら、どの設定ファイルが効いているか
・その中身はどうなっているか
・CMYK の PDF が正しく描画されるか

サーバー会社に問い合わせるとき、この情報があるかどうかで話の早さが変わります。

よくある方法では、分かりません

前回も書きましたが、大事なので繰り返します。

ImageMagick に「PDFに対応していますか」と尋ねる方法があります。これが、当てになりません。

答えているのは「PDFを扱う部品が組み込まれているか」で、使ってよいかどうかは別だからです。

部品はある。でも設定で禁止されている。そのとき、この方法は「使える」と答えます。

実際に読ませないと、分かりません。

プローブでやっていること

4つです。

1. 拡張が入っているかを見る。

2. 設定ファイルを探して、中身を読む。 有効なルールと、コメントアウトされているルールを分けて表示します。

3. 小さなPDFを、実際に読ませる。 数百バイトのものをコードの中で組み立てて、通るかどうかを見ます。

4. CMYKのPDFも読ませる。 シアン100%のページを作って、シアンとして描画されるかを確かめます。

外部のコマンドは使いません。 ファイルを読むのと、ImageMagick に尋ねるだけです。書き込みも通信もしません。

使い方

1. コードの中の TOKEN を、自分だけが知る文字列に書き換える
2. サーバーの公開ディレクトリに置く
3. ブラウザで開く
   https://example.com/probe-imagemagick.php?token=書き換えた文字列
4. 消す

4番を忘れないでください。 サーバーの絶対パスが表示されます。

消し忘れても、1時間で使えなくなります

とはいえ、4番は忘れられます。

なので、時間切れの仕組みを入れました。

ファイル自身の更新時刻から1時間が過ぎると、正しいトークンを入れても 404 を返すようになります。

Not found

This probe expired 60 minutes after upload.
Delete the file. To run it again, re-upload it (that resets the clock).

また測りたくなったら、アップロードし直せば使えます。 更新時刻がリセットされるので。

これは、消さなくていいという意味ではありません。 危険な時間を短くするだけです。確認したら消してください。

時間を変えたい場合

const LIFETIME = 3600;   // 秒。0 にすると期限なし

長くするのは勧めません。 短くするのは自由です。

コード

長いですが、全部必要です。 コピーして、`probe-imagemagick.php` という名前で保存してください。

<?php
/**
 * Standalone ImageMagick / PDF policy probe.
 *
 * Answers one question on a server you do not administer: can this host
 * render a PDF, and if not, is it because the extension is missing or
 * because policy.xml forbids the PDF coder?
 *
 * Not part of the plugin. tools/ is export-ignored, so this never ships.
 *
 * USAGE
 *   1. Edit TOKEN below to something only you know.
 *   2. Upload this file to the web root.
 *   3. Open https://example.com/probe-imagemagick.php?token=YOUR_TOKEN
 *   4. DELETE IT. It prints absolute server paths.
 *
 * Step 4 gets forgotten, so the file expires on its own: LIFETIME seconds
 * after its own mtime it returns 404 whatever the token says. Deleting it
 * is still the right move -- this only bounds the window.
 *
 * Read-only: no exec(), no writes, no network. It opens policy.xml files
 * for reading and asks Imagick what it supports. Nothing else.
 */

const TOKEN = 'CHANGE-ME';

/** Seconds the file stays usable after upload. 0 disables the expiry. */
const LIFETIME = 3600;

if (!isset($_GET['token']) || !hash_equals(TOKEN, (string) $_GET['token']) || 'CHANGE-ME' === TOKEN) {
    header('HTTP/1.1 404 Not Found');
    exit("Not found\n");
}

// Expire on the file's own mtime. Uploading sets it, so the clock starts
// when the file lands on the server -- no state to store anywhere.
if (LIFETIME > 0) {
    $mtime = @filemtime(__FILE__);
    if (false === $mtime) {
        header('HTTP/1.1 404 Not Found');
        exit("Not found\n");
    }
    $age = time() - $mtime;
    if ($age > LIFETIME) {
        header('HTTP/1.1 404 Not Found');
        header('Content-Type: text/plain; charset=UTF-8');
        printf(
            "Not found\n\nThis probe expired %d minutes after upload.\n"
            . "Delete the file. To run it again, re-upload it (that resets the clock).\n",
            (int) (LIFETIME / 60)
        );
        exit;
    }
}

header('Content-Type: text/plain; charset=UTF-8');

function h(string $title): void
{
    echo "\n" . $title . "\n" . str_repeat('-', strlen($title)) . "\n";
}

function kv(string $k, $v): void
{
    printf("  %-26s %s\n", $k, is_bool($v) ? var_export($v, true) : (string) $v);
}

/**
 * Closing reminder. Called from every exit path, including the early one
 * for a missing extension -- that path still printed server paths above.
 */
function footer(): void
{
    echo "\n" . str_repeat('=', 60) . "\n";
    echo "確認したらこのファイルを削除してください。\n";
    if (LIFETIME > 0) {
        $left = LIFETIME - (time() - (int) @filemtime(__FILE__));
        printf("消し忘れても、あと約 %d 分で 404 を返すようになります。\n", (int) max(0, $left / 60));
    }
}

echo "ImageMagick / PDF probe\n";
echo str_repeat('=', 60) . "\n";

h('PHP');
kv('version', PHP_VERSION);
kv('sapi', PHP_SAPI);
kv('uname', php_uname('s') . ' ' . php_uname('r') . ' ' . php_uname('m'));

h('Imagick extension');
$hasExt = extension_loaded('imagick');
kv('extension_loaded(imagick)', $hasExt);
kv('class_exists(Imagick)', class_exists('Imagick'));

if (!$hasExt || !class_exists('Imagick')) {
    h('VERDICT');
    echo "  拡張がない (no_extension)\n";
    echo "  → ホスティング事業者に Imagick PHP 拡張の導入を依頼する。\n";
    footer();
    exit;
}

$version = Imagick::getVersion();
kv('ImageMagick', $version['versionString'] ?? '?');
kv('extension version', phpversion('imagick'));

h('Format support');
foreach (['PDF', 'PS', 'EPS', 'PNG', 'JPEG', 'WEBP'] as $format) {
    try {
        $supported = Imagick::queryFormats($format);
    } catch (Throwable $e) {
        $supported = [];
    }
    kv($format, empty($supported) ? 'NO' : 'yes');
}

$pdfOk = false;
try {
    $pdfOk = !empty(Imagick::queryFormats('PDF'));
} catch (Throwable $e) {
    $pdfOk = false;
}

h('Where ImageMagick looks for configuration');
$dirs = [];

$env = getenv('MAGICK_CONFIGURE_PATH');
kv('MAGICK_CONFIGURE_PATH', false === $env || '' === $env ? '(unset)' : $env);
if (is_string($env) && '' !== $env) {
    $dirs = array_merge($dirs, explode(':', $env));
}

$home = getenv('MAGICK_HOME');
kv('MAGICK_HOME', false === $home || '' === $home ? '(unset)' : $home);
if (is_string($home) && '' !== $home) {
    $dirs[] = rtrim($home, '/') . '/etc/ImageMagick-7';
    $dirs[] = rtrim($home, '/') . '/etc/ImageMagick-6';
    $dirs[] = rtrim($home, '/') . '/config-Q16';
}

if (method_exists('Imagick', 'getConfigureOptions')) {
    try {
        foreach ((array) Imagick::getConfigureOptions('CONFIGURE_PATH') as $key => $value) {
            kv('getConfigureOptions', (is_string($key) ? $key . '=' : '') . $value);
            if (is_string($value) && '' !== $value) {
                $dirs = array_merge($dirs, explode(':', $value));
            }
        }
    } catch (Throwable $e) {
        kv('getConfigureOptions', 'threw: ' . $e->getMessage());
    }
} else {
    kv('getConfigureOptions', '(not available in this build)');
}

$dirs = array_merge($dirs, [
    '/etc/ImageMagick-7',
    '/etc/ImageMagick-6',
    '/etc/ImageMagick',
    '/usr/local/etc/ImageMagick-7',
    '/usr/local/etc/ImageMagick-6',
    '/opt/homebrew/etc/ImageMagick-7',
]);

h('policy.xml files found');
$found = [];
$seen = [];
foreach ($dirs as $dir) {
    $dir = rtrim(trim((string) $dir), '/');
    if ('' === $dir) {
        continue;
    }
    $file = $dir . '/policy.xml';
    if (isset($seen[$file])) {
        continue;
    }
    $seen[$file] = true;

    if (!is_readable($file)) {
        printf("  %-56s %s\n", $file, is_file($file) ? 'exists, unreadable' : '-');
        continue;
    }
    printf("  %-56s %s\n", $file, 'READABLE');
    $found[] = $file;
}

if (!$found) {
    echo "\n  読める policy.xml は見つかりませんでした。\n";
}

foreach ($found as $file) {
    h('coder rules in ' . $file);
    $xml = @file_get_contents($file);
    if (false === $xml || '' === $xml) {
        echo "  (empty)\n";
        continue;
    }

    // Strip comments FIRST. A rule inside <!-- --> is switched off, and
    // listing it as active sends the reader to the wrong conclusion.
    $active = preg_replace('/<!--.*?-->/s', '', $xml);
    $commented = [];
    if (preg_match_all('/<!--(.*?)-->/s', $xml, $cm)) {
        foreach ($cm[1] as $chunk) {
            if (preg_match_all('/<policy\b[^>]*>/i', $chunk, $inner)) {
                foreach ($inner[0] as $tag) {
                    $commented[] = trim($tag);
                }
            }
        }
    }

    echo "  [有効] --------------------------------------------------\n";
    $printed = 0;
    if (preg_match_all('/<policy\b[^>]*>/i', (string) $active, $m)) {
        foreach ($m[0] as $tag) {
            if (!preg_match('/\bdomain\s*=\s*"([^"]*)"/i', $tag, $d)) {
                continue;
            }
            if (!in_array(strtolower(trim($d[1])), ['coder', 'delegate', 'module'], true)) {
                continue;
            }
            echo '  ' . trim($tag) . "\n";
            $printed++;
        }
    }
    if (0 === $printed) {
        echo "  (coder/delegate/module の有効なルールなし)\n";
    }

    if ($commented) {
        echo "\n  [コメントアウト済み = 効いていない] ----------------------\n";
        foreach ($commented as $tag) {
            echo '  ' . $tag . "\n";
        }
    }
}

h('Actual read test');

/**
 * Build a minimal, structurally valid one-page PDF in memory.
 *
 * queryFormats() reports which coders were compiled in. Whether it also
 * applies the security policy turns out to differ between ImageMagick
 * builds, so it cannot settle the question on its own. Actually asking
 * ImageMagick to read a PDF can.
 */
function minimal_pdf(): string
{
    $objects = [
        "<< /Type /Catalog /Pages 2 0 R >>",
        "<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
        "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 72 72] /Resources << >> >>",
    ];

    $pdf = "%PDF-1.4\n";
    $offsets = [];
    foreach ($objects as $i => $body) {
        $offsets[] = strlen($pdf);
        $pdf .= ($i + 1) . " 0 obj\n" . $body . "\nendobj\n";
    }

    $xref = strlen($pdf);
    $pdf .= "xref\n0 " . (count($objects) + 1) . "\n";
    $pdf .= "0000000000 65535 f \n";
    foreach ($offsets as $offset) {
        $pdf .= sprintf("%010d 00000 n \n", $offset);
    }
    $pdf .= "trailer\n<< /Size " . (count($objects) + 1) . " /Root 1 0 R >>\n";
    $pdf .= "startxref\n" . $xref . "\n%%EOF\n";

    return $pdf;
}

$readOk = false;
$readError = '';
try {
    $im = new Imagick();
    $im->setResolution(72, 72);
    $im->readImageBlob(minimal_pdf(), 'probe.pdf');
    kv('readImageBlob', 'OK');
    kv('rendered size', $im->getImageWidth() . ' x ' . $im->getImageHeight());
    $im->clear();
    $readOk = true;
} catch (Throwable $e) {
    $readError = $e->getMessage();
    kv('readImageBlob', 'FAILED');
    echo "\n  " . trim($readError) . "\n";
}

// ImageMagick 7 は "NotAuthorized `PDF'"、6 は "not authorized" と綴りが
// 違う。空白の有無で取りこぼすので \s* にしてある。
$notAuthorized = (bool) preg_match('/not\s*authoriz|security policy/i', $readError);
$noDelegate = (bool) preg_match('/no decode delegate|delegate.*(failed|missing)|FailedToExecuteCommand|gs.*not found/i', $readError);

h('CMYK read test');

/**
 * A one-page PDF whose only content is a solid CMYK fill.
 *
 * ImageMagick picks its Ghostscript device from what it finds in the PDF.
 * DeviceCMYK sends ImageMagick 6 down the bmpsep8 path, which is where the
 * blank-thumbnail bug lives. A pure white result here is the bug.
 */
function minimal_cmyk_pdf(): string
{
    $stream = "/DeviceCMYK cs\n1.0 0.0 0.0 0.0 k\n0 0 72 72 re\nf\n";

    $objects = [
        "<< /Type /Catalog /Pages 2 0 R >>",
        "<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
        "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 72 72] "
            . "/Resources << /ColorSpace << /CS0 /DeviceCMYK >> >> /Contents 4 0 R >>",
        "<< /Length " . strlen($stream) . " >>\nstream\n" . $stream . "endstream",
    ];

    $pdf = "%PDF-1.4\n";
    $offsets = [];
    foreach ($objects as $i => $body) {
        $offsets[] = strlen($pdf);
        $pdf .= ($i + 1) . " 0 obj\n" . $body . "\nendobj\n";
    }

    $xref = strlen($pdf);
    $pdf .= "xref\n0 " . (count($objects) + 1) . "\n";
    $pdf .= "0000000000 65535 f \n";
    foreach ($offsets as $offset) {
        $pdf .= sprintf("%010d 00000 n \n", $offset);
    }
    $pdf .= "trailer\n<< /Size " . (count($objects) + 1) . " /Root 1 0 R >>\n";
    $pdf .= "startxref\n" . $xref . "\n%%EOF\n";

    return $pdf;
}

$cmykOk = null;
$cmykBlank = false;
if (!$readOk) {
    echo "  (RGB の読み込みが通っていないので省略)\n";
} else {
    try {
        $im = new Imagick();
        $im->setResolution(72, 72);
        $im->readImageBlob(minimal_cmyk_pdf(), 'probe-cmyk.pdf');
        kv('readImageBlob', 'OK');
        kv('size', $im->getImageWidth() . ' x ' . $im->getImageHeight());
        // Which Ghostscript device ImageMagick chose shows up here. A CMYK
        // colorspace means it went down the separation path -- the one that
        // breaks on ImageMagick 6.
        $space = $im->getImageColorspace();
        $spaceName = 'id ' . $space;
        if (defined('Imagick::COLORSPACE_CMYK') && Imagick::COLORSPACE_CMYK === $space) {
            $spaceName = 'CMYK (id ' . $space . ')';
        } elseif (defined('Imagick::COLORSPACE_SRGB') && Imagick::COLORSPACE_SRGB === $space) {
            $spaceName = 'sRGB (id ' . $space . ')';
        }
        kv('colorspace', $spaceName);
        kv('unique colours', count($im->getImageHistogram()));

        // Sample in sRGB so the number below means what it looks like. Read
        // straight out of a CMYK raster, 100% cyan reports as r=255, which
        // reads like red to anyone looking at the output.
        try {
            $im->transformImageColorspace(Imagick::COLORSPACE_SRGB);
        } catch (Throwable $e) {
            // Leave it in whatever space it came back in.
        }

        $px = $im->getImagePixelColor((int) ($im->getImageWidth() / 2), (int) ($im->getImageHeight() / 2));
        $rgb = $px->getColor();
        $hex = sprintf('#%02X%02X%02X', $rgb['r'], $rgb['g'], $rgb['b']);
        kv('centre pixel (sRGB)', $hex . '  (r=' . $rgb['r'] . ' g=' . $rgb['g'] . ' b=' . $rgb['b'] . ')');

        // A page filled edge to edge with 100% cyan cannot legitimately come
        // back white or black. Either one means the raster is empty.
        $cmykBlank = ($rgb['r'] > 240 && $rgb['g'] > 240 && $rgb['b'] > 240)
            || ($rgb['r'] < 15 && $rgb['g'] < 15 && $rgb['b'] < 15);
        $cmykOk = !$cmykBlank;

        $im->clear();
    } catch (Throwable $e) {
        $cmykOk = false;
        kv('readImageBlob', 'FAILED');
        echo "\n  " . trim($e->getMessage()) . "\n";
    }

    echo "\n";
    if (true === $cmykOk) {
        echo "  シアン 100% がシアンとして描画されました。CMYK の PDF も扱えます。\n";
    } elseif ($cmykBlank) {
        echo "  ★ シアン 100% が真っ白(または真っ黒)になりました。\n";
        echo "    CMYK の PDF から作るサムネイルは、この環境では潰れます。\n";
    } else {
        echo "  ★ CMYK の PDF を読めませんでした。上のエラーを参照。\n";
    }
}

h('ImageMagick generation');
$major = 0;
if (preg_match('/ImageMagick\s+(\d+)\./', (string) ($version['versionString'] ?? ''), $mv)) {
    $major = (int) $mv[1];
}
kv('major version', $major ?: '?');
if (6 === $major) {
    echo "\n  ImageMagick 6 です。PDF が読めても、CMYK の PDF は真っ白な画像に\n";
    echo "  なることがあります(bmpsep8 デバイスの出力を IM6 自身の BMP\n";
    echo "  リーダーが解釈できないため)。RGB の PDF には影響しません。\n";
}

h('VERDICT');
if ($readOk) {
    echo "  PDF を実際に読めました (ok)\n";
    echo "  → このサーバーでサムネイル生成は動きます。\n";
    if (false === $cmykOk) {
        echo "  ただし CMYK の PDF は潰れます(上の CMYK read test 参照)。\n";
    } elseif (true === $cmykOk) {
        echo "  CMYK の PDF も実測で通りました。\n";
    } elseif (6 === $major) {
        echo "  ただし CMYK の PDF は真っ白になる可能性があります。\n";
    }
} elseif ($notAuthorized) {
    echo "  ポリシーで拒否されました (pdf_blocked_by_policy)\n";
    echo "  → policy.xml で PDF コーダーに read 権限を、と依頼する。\n";
} elseif ($noDelegate) {
    echo "  デリゲートが見つかりません (pdf_unsupported)\n";
    echo "  → Ghostscript の導入と ImageMagick の PDF 対応を依頼する。\n";
} else {
    echo "  PDF を読めませんでした。上のエラーを読んでください。\n";
}

echo "\n  参考: queryFormats('PDF') は " . ($pdfOk ? "PDF を返しました" : "空でした") . "。\n";
if ($pdfOk !== $readOk) {
    echo "  ★ queryFormats と実際の読み込みで結果が食い違っています。\n";
    if ($notAuthorized) {
        echo "    queryFormats は PDF を返すのに、読み込みはポリシーで拒否\n";
        echo "    されました。この ImageMagick の queryFormats はポリシーを\n";
        echo "    反映しません。判定には実読み込みを使ってください。\n";
    } elseif ($noDelegate) {
        echo "    コーダーは登録されていますが、実際に描画する Ghostscript が\n";
        echo "    呼べません。ポリシーの問題ではありません。\n";
    } else {
        echo "    判定には実読み込みのほうを使ってください。\n";
    }
}

h('OLD VERDICT (queryFormats のみによる判定・参考)');
if ($pdfOk) {
    echo "  PDF は許可されています (ok)\n";
    echo "  → このサーバーでプラグインは動きます。\n";
} elseif ($found) {
    echo "  PDF が使えません。上の coder ルールを読んでください。\n";
    echo "  pattern に PDF / PS / {…,PDF,…} / * を含み rights が none または\n";
    echo "  read を含まないものがあれば、それが原因です (pdf_blocked_by_policy)。\n";
    echo "  → policy.xml で PDF コーダーに read 権限を、と依頼する。\n";
} else {
    echo "  PDF が使えず、policy.xml も読めませんでした (pdf_unsupported)。\n";
    echo "  → ビルドに PDF デリゲートが無い可能性が高い。ImageMagick の\n";
    echo "     PDF 対応と policy.xml の両方を確認するよう依頼する。\n";
}

footer();



出力の読み方

いちばん下の「VERDICT」を先に見てください。 そこに答えが出ます。

動く場合

PDF を実際に読めました (ok)
→ このサーバーでサムネイル生成は動きます。
CMYK の PDF も実測で通りました。

これが出れば、問題ありません。

WordPress の標準機能でも、プラグインを使う場合でも、サムネイルは生成されます。

ポリシーで禁止されている場合

ポリシーで拒否されました (pdf_blocked_by_policy)
→ policy.xml で PDF コーダーに read 権限を、と依頼する。

ImageMagick は入っているが、設定で禁止されている状態です。

サーバー会社に、設定の変更を依頼することになります。

拡張が入っていない場合

拡張がない (no_extension)
→ ホスティング事業者に Imagick PHP 拡張の導入を依頼する。

Ghostscript がない場合

デリゲートが見つかりません (pdf_unsupported)
→ Ghostscript の導入と ImageMagick の PDF 対応を依頼する。

ImageMagick は、PDFの描画を別のプログラムに任せています。 それが無い状態です。

設定ファイルの読み方

ここが、この記事でいちばん伝えたい部分です。

プローブは、設定ファイルの中身を2つに分けて表示します。

[有効] --------------------------------------------------
<policy domain="coder" rights="none" pattern="EPHEMERAL" />
<policy domain="coder" rights="none" pattern="HTTPS" />
(ほか7件)

[コメントアウト済み = 効いていない] ----------------------
<policy domain="coder"  rights="none"       pattern="*" />
<policy domain="module" rights="none"       pattern="{PS,PDF,XPS}" />
(ほか多数)

なぜ分けているのか

設定ファイルを開いて「PDF」を検索すると、禁止しているように見える行が見つかります。

でも、コメントの中にあることが多いんです。

ImageMagick の公式ファイルには、設定例がコメントとして書かれています。 そして、サーバー会社がPDFを許可するとき、行を削除せずコメントアウトしたままにします。

だから、素朴に検索すると誤読します。

実際、前回測ったサーバーはPDFを許可していたのに、設定ファイルには「PS/PDF/XPS 禁止」と書かれた行がありました。コメントの中でしたが。

サーバー会社への問い合わせ

禁止されていた場合、依頼することになります。

プローブの出力を貼るのが、いちばん早いと思います。

お世話になっております。

WordPress で PDF のサムネイル生成を利用したいのですが、
ImageMagick のセキュリティポリシーで PDF の読み込みが
禁止されているようです。

確認した内容は以下のとおりです。

(プローブの出力を貼る)

policy.xml において、PDF コーダーに read 権限を
付与していただくことは可能でしょうか。

ご確認のほど、よろしくお願いいたします。

「動きません」だけより、ずっと話が早くなります。

ただし、断られることもあります。 セキュリティ上の判断なので、そこは仕方がありません。

同じ判定を、プラグインにも入れました

この記事のプローブは、単体で動く確認用のものです。

ただ、毎回これを置いて測るのは面倒です。 そして、サーバーの構成は変わります。

そこで、同じ判定を、自分のプラグインの中にも入れました。

Rapls PDF Image Creator という、PDFのサムネイルを作るプラグインです。

管理画面と、WordPress のサイトヘルスに、いまの状態が出ます。 上に書いた4つの状態を、そのまま表示する形です。

PDF blocked by ImageMagick policy
ImageMagick は入っていますが、セキュリティポリシーで
PDF の読み込みが禁止されています。
これはサーバーの設定であって、部品の不足ではありません。

やること: policy.xml で PDF コーダーに read 権限を
         付けてもらうよう、ホスティング事業者に依頼する
設定ファイル: /etc/ImageMagick-6/policy.xml

サイトヘルスの「情報」にも出るので、そのままサポートに貼れます。

ただ、判定そのものは、この記事のプローブと同じです。 測るだけなら、プローブで足ります。

注意すること

トークンを変えてください

`CHANGE-ME` のままだと、動きません。 わざとそうしてあります。

推測されにくい文字列にしてください。 短いものや、単語そのものは避けてください。

公開ディレクトリに置きます

ブラウザから開く必要があるので、公開される場所に置きます。

つまり、URLを知っていれば誰でもアクセスできる状態になります。トークンで守っていますが、それだけです。

だから、消してください。

絶対パスが出ます

サーバー内のファイルの場所が、そのまま表示されます。

スクリーンショットを公開するときは、そこを隠してください。

本番サイトで測る場合

読むだけなので、サイトへの影響はありません。 書き込みも設定変更もしません。

それでも気になる場合は、テスト用のサブドメインなどで測ってください。 ただし、環境が違うと結果も違います。

測ったあと

結果を控えておくと、あとで役に立ちます。

サーバーを乗り換えるとき、プランを変えるとき、PHPの版を上げるとき。そのたびに変わる可能性があります。

そして、測った日付も一緒に残してください。サーバーの構成は変わります。

ちなみに、前回測ったのはレンタルサーバー エックスサーバーのスタンダードプランです。2026年8月29日の時点では、PDFが許可されていました。

まとめ

  • サイトヘルスでは、PDFが許可されているかまでは分からない
  • 「対応しているか」を尋ねる方法は当てにならない。実際に読ませる
  • プローブはPHPファイル1つ。 WordPressもプラグインも要らない
  • やることは4つ。トークンを変える/置く/開く/消す
  • 消し忘れても、1時間で404を返すようにしてある
  • また使いたいときは、アップロードし直す
  • いちばん下のVERDICTに答えが出る
  • 設定ファイルは、有効なものとコメントアウトされたものを分けて表示
  • コメントの中の行は効いていない。 素朴に検索すると誤読する
  • 禁止されていたら、出力を貼って問い合わせる
  • 絶対パスが出るので、スクリーンショットに注意

測るのは、5分もかかりません。

そして、測らないと分からないことでした。 記事を読んで「たぶんこうだろう」と思うより、自分の環境で確かめるほうが確実です。

レンタルサーバー エックスサーバー



※本記事のコードは2026年8月時点のものです。PHP 8.3 で動作を確認しています。

タイトルとURLをコピーしました