Something most tutorials skip that's worth knowing upfront: when you upload an image to WordPress, it automatically generates multiple thumbnail sizes. A single original image can spawn 5–8 additional copies at different dimensions. What looks like 2,000 images in your media library might mean more than 10,000 files actually sitting on the server.
So before reaching for any tool, get a clear picture of what you're actually dealing with:
# Count total image files
find /www/wwwroot/example.com/wp-content/uploads \
-type f \
-iname "*.jpg" -o \
-iname "*.jpeg" -o \
-iname "*.png" | wc -l
# Check total directory size
du -sh /www/wwwroot/example.com/wp-content/uploads
Once you have those numbers, you'll have a realistic sense of what the rest of this process involves.
Backup First — This Step Is Not Optional
Batch compression modifies original files in place. There's no undo. Back up before running any compression command:
cd /www/wwwroot/example.com
tar -czf ~/uploads-backup-$(date +%Y%m%d).tar.gz \
wp-content/uploads
This will take a while if you have a large image library — let it run. If your VPS supports snapshots (DigitalOcean and Vultr both do), taking one before starting is an even safer option.
Don't proceed until the backup is complete.
Method 1: jpegoptim for Bulk JPG Compression
jpegoptim is purpose-built for JPEG files, supports quality parameters, and is the most straightforward tool for handling JPG images.
Install on Ubuntu / Debian:
apt update && apt install jpegoptim -y
Test on a single image first to see the results:
jpegoptim --max=85 --stdout test.jpg > /dev/null
Once you're satisfied with the output, run it across the entire uploads directory:
find /www/wwwroot/example.com/wp-content/uploads \
-type f \
\( -iname "*.jpg" -o -iname "*.jpeg" \) \
-exec jpegoptim --max=85 {} \;
--max=85 caps JPEG quality at 85, which is a reasonable balance for most website images. If your images are already low quality, you can drop this to 80 — but test a sample batch first rather than applying it globally.
For lossless optimization only (strips EXIF and other metadata without affecting image quality):
find /www/wwwroot/example.com/wp-content/uploads \
-type f \
\( -iname "*.jpg" -o -iname "*.jpeg" \) \
-exec jpegoptim --strip-all {} \;
Method 2: optipng for PNG Compression
PNG is the right format for transparent backgrounds, logos, and icons — don't apply JPEG compression logic to these. optipng performs lossless PNG compression with no quality loss.
Install:
apt install optipng -y
Batch process all PNGs:
find /www/wwwroot/example.com/wp-content/uploads \
-type f -iname "*.png" \
-exec optipng -o2 {} \;
-o2 sets the optimization level. The range is 0–7 — higher values compress more but take longer. Level 2 balances speed and results well for production servers. If the server has plenty of idle time, -o4 is fine, but avoid running high optimization levels during peak traffic hours.
Method 3: ImageMagick for General-Purpose Processing
ImageMagick is the most versatile option — it handles JPG and PNG compression, format conversion, and batch operations in one tool.
Install:
apt install imagemagick -y
Verify the installation:
magick -version
Test on a single image without overwriting the original:
magick input.jpg \
-strip \
-quality 85 \
output.jpg
-strip removes EXIF data, ICC color profiles, and other metadata — typically shaves off another 5–15% of file size with no visible impact on web display.
For batch processing, the script below is safer than running find -exec magick directly against original files. It writes to a temporary file first, compares sizes, and only replaces the original if the compressed version is actually smaller:
#!/bin/bash
UPLOAD_DIR="/www/wwwroot/example.com/wp-content/uploads"
QUALITY=85
find "$UPLOAD_DIR" \
-type f \
\( -iname "*.jpg" -o -iname "*.jpeg" \) | \
while read -r img; do
tmp="${img}.tmp.jpg"
magick "$img" -strip -quality $QUALITY "$tmp"
orig_size=$(stat -c%s "$img")
new_size=$(stat -c%s "$tmp")
if [ "$new_size" -lt "$orig_size" ]; then
mv "$tmp" "$img"
echo "Compressed: $img ($orig_size -> $new_size)"
else
rm "$tmp"
echo "Skipped (no gain): $img"
fi
done
Save this as compress.sh, make it executable, and run it:
chmod +x compress.sh
bash compress.sh
Method 4: Converting to WebP
WebP typically produces smaller files than JPEG at equivalent quality, and browser support is now very high. Worth considering in 2026.
Install cwebp:
apt install webp -y
Test a single conversion:
cwebp -q 82 input.jpg -o output.webp
Batch convert:
find /www/wwwroot/example.com/wp-content/uploads \
-type f \
\( -iname "*.jpg" -o -iname "*.jpeg" \) | \
while read -r img; do
cwebp -q 82 "$img" \
-o "${img%.jpg}.webp" 2>/dev/null
done
Important: Don't delete the original JPG files after conversion. WordPress's media library still references the JPG paths in the database — removing them causes 404 errors. The correct approach is to install a WebP-aware plugin (such as WebP Express) that serves WebP to browsers that support it and falls back to JPG for those that don't, with both files coexisting on the server.
Also: do not simply rename .jpg files to .webp. Changing the extension doesn't change the format — the file will be corrupted. Real conversion requires re-encoding with a proper tool.
A Note on AVIF
AVIF generally achieves better compression than WebP, but encoding is significantly slower and CPU-intensive during batch processing. For most VPS users hosting websites, WebP is the more practical choice right now. If your image library is small or your server has substantial idle compute capacity, avifenc is worth experimenting with — but it's outside the scope of this guide.
Managing VPS Resource Usage During Processing
Batch image processing puts load on both CPU and disk I/O. Running it on a live production server will affect site performance. Use nice and ionice to lower the task's resource priority:
nice -n 15 ionice -c 3 \
find /www/wwwroot/example.com/wp-content/uploads \
-type f \
\( -iname "*.jpg" -o -iname "*.jpeg" \) \
-exec jpegoptim --max=85 {} \;
nice -n 15 deprioritizes CPU usage; ionice -c 3 tells the kernel to run disk I/O only when the system is otherwise idle. Running during off-peak hours (early morning) is also a good strategy — schedule it with cron:
# Run at 3 AM every day
crontab -e
0 3 * * * nice -n 15 bash /root/compress.sh >> /root/compress.log 2>&1
Verifying the Results
After compression completes, use ImageMagick to verify file integrity across the uploads directory:
find /www/wwwroot/example.com/wp-content/uploads \
-type f \
\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" \) \
-exec magick identify {} \; > /root/image-check.txt 2>&1
Review image-check.txt for any errors — corrupted files will show up here and can be restored from the backup. Also open a few WordPress pages in the browser at random to confirm images are rendering correctly and thumbnails look fine.
FAQ
Does compressing WordPress images affect SEO?
No — smaller file sizes mean faster load times, which positively affects Core Web Vitals and SEO. Just make sure compression quality stays at an acceptable level; visibly degraded images are a different problem.
What's the right --max value for jpegoptim?
No universal answer. 80–85 is a common starting point, but it depends on the image type. Product photography and high-quality photos are more sensitive to quality reduction; screenshots and icons are less so. Test 10–20 representative images before applying any setting across the full library.
Can I convert PNG to JPG to reduce file size?
Yes, but with a caveat: PNG files with transparent backgrounds will have that transparency filled with white or black when converted to JPG, which breaks the image. Logos and icons typically shouldn't be converted. Photographic PNGs with no transparency convert to JPG without issue.
Do WordPress thumbnails need to be regenerated after compression?
No. jpegoptim and optipng modify files in place — file names and paths stay the same, so WordPress database references are unaffected. WebP conversion creates new files and leaves existing thumbnails untouched.
Can a low-spec VPS (1 GB RAM) handle these commands?
Yes, but use nice to lower the priority and run during off-peak hours to avoid impacting live traffic. AVIF conversion is CPU-heavy — batch AVIF encoding is not recommended on a 1 GB RAM server.
What should I clean up afterward?
Once you've confirmed compression results look good, the backup archive (uploads-backup-*.tar.gz) can be deleted or moved to object storage to free up local disk space. Keep or discard the compression script based on whether you plan to run it again.