Тяжёлые 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.
- webp –
cwebp/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.
Кеш и проверка
- После смены Nginx/Apache очистите page cache / FastCGI cache / CDN.
- Откройте сайт в Chrome, F12 → Network, отфильтруйте Img: ожидается WebP при наличии файла.
- Проверьте ответ с
curl -H 'Accept: image/webp' -I URL.jpgи без этого заголовка.
Итог
Схема простая: сжать оригиналы (jpegoptim/optipng/gifsicle), сгенерировать соседние .webp, отдать их rewrite-ом или PHP-фильтром, не ломая URL в WordPress, и повесить cron, чтобы новые загрузки догонялись ночью. Так ускоряется отдача медиа без обязательной миграции всей медиатеки на один формат.


