Как сжать и оптимизировать изображения для WordPress сайта на Linux: cron и WebP

Тяжёлые JPEG/PNG в wp-content/uploads часто съедают PageSpeed сильнее, чем тема или плагины. Ниже практичная схема для Linux-сервера: сжатие исходников утилитами CLI, генерация WebP рядом с оригиналами, отдача WebP без правок URL в БД WordPress и ночная автоматизация через cron.

Установка утилит

На Debian/Ubuntu:

sudo apt update
sudo apt install jpegoptim optipng gifsicle webp -y
  • jpegoptim – сжатие JPEG (можно с ограничением качества).
  • optipng – lossless-сжатие PNG.
  • gifsicle – оптимизация GIF.
  • webpcwebp / dwebp для конвертации в WebP.

Путь к медиа подставьте свой (ниже пример /var/www/html/wp-content/uploads).

Сжатие JPEG, PNG и GIF

JPEG: jpegoptim

jpegoptim --max=85 --strip-all image.jpg
  • --max=85 – не выше 85% качества (lossy, если исходник был выше).
  • --strip-all – убрать EXIF и прочие метаданные.

По всем JPEG в uploads:

find /var/www/html/wp-content/uploads -type f \( -iname '*.jpg' -o -iname '*.jpeg' \) \
  -exec jpegoptim --max=85 --strip-all {} \;

Важно: повторный прогон с --max=85 на уже сжатых файлах обычно безопасен (качество не «уронят» ниже порога), но первый проход лучше сделать на копии каталога или с бэкапом.

PNG: optipng

optipng -o7 image.png

-o7 – самый долгий режим lossless. На тысячах PNG ночной cron может занять заметное время; для регулярных прогонов иногда берут -o2/-o3.

find /var/www/html/wp-content/uploads -type f -iname '*.png' \
  -exec optipng -o7 {} \;

GIF: gifsicle

gifsicle -O3 image.gif -o image-optimized.gif
# in-place:
gifsicle -b -O3 image.gif

Конвертация в WebP

WebP рядом с оригиналом (имя то же, расширение .webp):

cwebp -q 80 image.jpg -o image.webp

Массово для JPEG/PNG, не перетирая уже существующие WebP без нужды:

find /var/www/html/wp-content/uploads -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' \) \
  -exec sh -c '
    for f; do
      out="${f%.*}.webp"
      [ -f "$out" ] && [ "$out" -nt "$f" ] && continue
      cwebp -q 80 "$f" -o "$out"
    done
  ' sh {} +

Оригиналы в медиатеке WordPress не трогаем: в HTML по-прежнему могут быть .jpg/.png. Отдавать WebP будет веб-сервер или фильтр темы.

Почему нельзя просто заменить URL в БД

Слепой search-replace .jpg на .webp в wp_posts / wp_postmeta ломает клиентов без WebP, ресайзы, CDN и плагины, которые ждут исходный MIME. Надёжнее: хранить оба файла и подменять ответ по Accept: image/webp, если рядом лежит .webp.

Отдача WebP на Apache (.htaccess)

В корне сайта (или в каталоге uploads), если включены mod_rewrite и доступ к .htaccess:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_ACCEPT} image/webp
RewriteCond %{REQUEST_FILENAME} (.+)\.(jpe?g|png)$
RewriteCond %{DOCUMENT_ROOT}%1.webp -f
RewriteRule (.+)\.(jpe?g|png)$ $1.webp [T=image/webp,E=ACCEPT:image/webp,L]
</IfModule>

<IfModule mod_headers.c>
Header append Vary Accept env=REDIRECT_ACCEPT
</IfModule>

Логика: браузер заявил WebP, запрошен JPEG/PNG, файл имя.webp есть, значит отдать его с типом image/webp. Заголовок Vary: Accept важен для CDN и прокси, чтобы не закешировать WebP для всех клиентов.

Отдача WebP на Nginx

Фрагмент server/location (пути подставьте свои). Вариант через map + try_files без тяжёлых if:

# в http {} или вместе с site-конфигом
map $http_accept $webp_suffix {
    default   "";
    "~*webp"  ".webp";
}

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    location ~* ^/wp-content/uploads/(.+)\.(jpe?g|png)$ {
        add_header Vary Accept;
        try_files $uri$webp_suffix $uri =404;
    }
}

Проверка и reload:

sudo nginx -t
sudo systemctl reload nginx

В DevTools → Network у запросов к картинкам должен быть тип WebP (или URL с .webp в rewrite-сценариях), а у клиентов без WebP – исходный JPEG/PNG.

Вариант на PHP (functions.php / must-use)

Если rewrite на сервере недоступен, можно подменять src в контенте. Упрощённый пример (лучше вынести в mu-plugin и тщательно протестировать пути):

<?php
function serve_webp_if_available( $content ) {
    if ( empty( $_SERVER['HTTP_ACCEPT'] ) || strpos( $_SERVER['HTTP_ACCEPT'], 'image/webp' ) === false ) {
        return $content;
    }

    return preg_replace_callback(
        '/<img[^>]+src=["\']([^"\']+)\.(jpe?g|png)["\']/i',
        function ( $m ) {
            $webp_url = $m[1] . '.webp';
            $path     = str_replace( site_url( '/' ), ABSPATH, $webp_url );
            if ( file_exists( $path ) ) {
                return str_replace( '.' . $m[2], '.webp', $m[0] );
            }
            return $m[0];
        },
        $content
    );
}
add_filter( 'the_content', 'serve_webp_if_available' );

Минусы: не покрывает все теги/lazy-load/srcset, бьёт по кешу HTML. Для продакшена обычно надёжнее rewrite на Apache/Nginx или плагин с поддержкой WebP на уровне медиа.

Автоматизация через cron

crontab -e

Пример ночного расписания (пути и пользователя www-data подстройте; логируйте вывод):

# Сжатие JPEG/PNG
0 2 * * * find /var/www/html/wp-content/uploads -type f \( -iname '*.jpg' -o -iname '*.jpeg' \) -exec jpegoptim --max=85 --strip-all {} \; >> /var/log/img-opt.log 2>&1
15 2 * * * find /var/www/html/wp-content/uploads -type f -iname '*.png' -exec optipng -o2 {} \; >> /var/log/img-opt.log 2>&1

# WebP только если файла ещё нет или оригинал новее
0 3 * * * find /var/www/html/wp-content/uploads -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' \) -exec sh -c 'for f; do out="${f%.*}.webp"; [ -f "$out" ] && [ "$out" -nt "$f" ] && continue; cwebp -q 80 "$f" -o "$out"; done' sh {} + >> /var/log/img-opt.log 2>&1

Права: процесс должен читать/писать uploads от имени пользователя с доступом к файлам веб-сервера. На больших медиатеках разнесите JPEG и PNG по времени, чтобы не упереться в I/O.

Кеш и проверка

  1. После смены Nginx/Apache очистите page cache / FastCGI cache / CDN.
  2. Откройте сайт в Chrome, F12 → Network, отфильтруйте Img: ожидается WebP при наличии файла.
  3. Проверьте ответ с curl -H 'Accept: image/webp' -I URL.jpg и без этого заголовка.

Итог

Схема простая: сжать оригиналы (jpegoptim/optipng/gifsicle), сгенерировать соседние .webp, отдать их rewrite-ом или PHP-фильтром, не ломая URL в WordPress, и повесить cron, чтобы новые загрузки догонялись ночью. Так ускоряется отдача медиа без обязательной миграции всей медиатеки на один формат.

Источники и ссылки


Комментарии загружаются…