---
title: "Production Tuning - PerfLocale"
description: "Four settings that make a PerfLocale site fly. Object cache, OPcache, CDN edge caching, and string-translation mode - with measured query counts."
canonical: "https://perflocale.com/docs/production-tuning/"
source: "https://perflocale.com/docs/production-tuning/"
format: "markdown"
---
# Production Tuning

Four switches. Measurable differences. No guesswork.

PerfLocale is fast by default. These four settings are what take it from fast to _imperceptible_, in order of impact.

## What to expect

Measured on the PerfLocale test harness - WordPress 6.9, PHP 8.4, MariaDB 10.6, 5 active languages, 200 translation groups, 1000 posts. Warm-cache second request. Query counts are deterministic and should reproduce closely on any stack; wall-clock timings depend on your hardware, hosting and other plugins, so they are deliberately not quoted here.

| Request | Default | With object cache | Query reduction |
| --- | --- | --- | --- |
| Homepage | 102 queries | 13 queries | −88% |
| Localised home (/de/) | 107 queries | 13 queries | −88% |
| Blog archive (10 posts) | 112 queries | 14 queries | −88% |

Query counts don’t scale with site size. Same numbers on a 15-post site and a 1000-post site - PerfLocale’s caches are bounded per request, not per total content.

Paginated archives get an extra optimization for free. On language-filtered front-end archive queries PerfLocale suppresses WordPress core’s deprecated `SQL_CALC_FOUND_ROWS` — which, combined with the language JOIN, forces MySQL to scan every matching row (ignoring `LIMIT`) just to count them — and supplies the pagination count from a generationally-cached `COUNT` instead. It’s on by default and needs no configuration; to restore core’s behaviour, `add_filter( 'perflocale/query/optimize_found_rows', '__return_false' )`.

## 1\. Persistent object cache

**The single biggest win.** Turns ~100 queries per page into ~13. Every WordPress site benefits; multilingual sites benefit most because PerfLocale reads small data structures (language list, translation links, slug translations) on every request — all of those become free.

### Redis (recommended)

```
# Install Redis + the PHP extension
sudo apt install php8.3-redis redis-server
sudo systemctl enable --now redis-server

# Install & activate the WordPress plugin
wp plugin install redis-cache --activate
wp redis enable
```

Verify:

```bash
wp eval 'echo wp_using_ext_object_cache() ? "YES" : "NO";'
# → YES
```

### Alternatives

-   **Memcached** - simpler, functionally equivalent for this workload. Install the [Memcached drop-in](https://wordpress.org/plugins/memcached/).
-   **Object Cache Pro** - commercial, tighter WooCommerce integration. Often bundled with managed hosts (Kinsta, Pressable, Cloudways).

## 2\. PHP OPcache

Caches compiled PHP bytecode. Bundled with PHP 5.5+, almost always available, but defaults are too small for WordPress - hot files get evicted. Give it room:

```
# /etc/php/8.3/fpm/php.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.interned_strings_buffer=32
opcache.validate_timestamps=0 ; production only - requires fpm reload on deploy
opcache.save_comments=1 ; required for WordPress
```

Verify:

```bash
wp eval 'print_r( opcache_get_status( false )["opcache_statistics"] );'
# Look for "opcache_hit_rate" — should be > 99% on a warm pool.
```

`validate_timestamps=0` means OPcache won’t notice file edits. `systemctl reload php-fpm` after every deploy. Don’t set this on a dev machine.

## 3\. CDN edge caching

The fastest request is the one PHP never sees. With a CDN in front, most visitors get HTML from the edge. PerfLocale ships two features that make edge caching work correctly on multilingual sites:

### Cache-Tag headers

Enable at _Settings → Advanced → CDN Cache-Tag Headers_. PerfLocale emits a tag on every response:

```
Cache-Tag: perflocale,lang:fr_FR,lang-slug:fr,post:42,post-type:post
```

Your CDN (Cloudflare Enterprise, Bunny, Fastly, KeyCDN…) can then purge surgically - flush everything tagged `lang:fr_FR`, or just `post:42`, without nuking the whole cache. Full reference: [Cache-Tag Headers](https://perflocale.com/docs/cache-tags/).

### Edge language detection

On Cloudflare Workers, Vercel Edge, or Netlify Edge, you can resolve the visitor’s language _at the edge_ and include it in the cache key. Result: `/` caches separately per language, zero round-trips to WordPress for language detection. Full guide: [Edge Integration](https://perflocale.com/docs/edge-integration/).

**One thing to avoid:** `Vary: Accept-Language`. Shreds hit rate - every distinct browser `Accept-Language` header produces a separate cache entry. PerfLocale deliberately does not emit this header. Use URL-based routing (`/de/`, subdomain, or per-domain) or edge-hint headers instead.

## 4\. String-translation mode

Under _Settings → Performance_, choose how UI string translations are stored:

| Mode | Speed | When to use |
| --- | --- | --- |
| Files (default) | No database read for UI strings | Default. Translations compiled into .l10n.php files - zero DB cost per string lookup. |
| Database | Slightly slower, zero filesystem writes | Read-only filesystems (some container / serverless WP deployments), or permission issues with the translations directory. |

For normal production servers, **keep the default**.

## 5\. Deny web access to the export directory

Not a speed setting, but it belongs in the same pass. Exports land in `wp-content/uploads/perflocale/exports/`, which is inside the web root. PerfLocale writes a `Deny from all` `.htaccess` there, and **nginx and Caddy ignore `.htaccess`**, so on those servers you need a real rule:

```nginx
location ~* /wp-content/uploads/perflocale/exports/ {
	deny all;
	return 404;
}
```

_Tools → Site Health_ checks this for you by writing a temporary random file and requesting it over HTTP. If it comes back, you get a critical result with the snippet. See [Security](https://perflocale.com/security/#server-hardening) for the full explanation.

## Measuring your own numbers

Drop this into `wp-content/mu-plugins/perf-log.php` for a tuning session. Remove it when done.

```php
<?php
if ( ! defined( 'SAVEQUERIES' ) ) define( 'SAVEQUERIES', true );
add_action( 'shutdown', function () {
	global $wpdb;
	$total = 0;
	foreach ( $wpdb->queries ?? [] as $q ) $total += (float) ( $q[1] ?? 0 );
	file_put_contents(
		WP_CONTENT_DIR . '/perf.log',
		sprintf( "%-40s queries=%3d time=%6.2fms\n",
			$_SERVER['REQUEST_URI'] ?? '?',
			count( $wpdb->queries ?? [] ),
			$total * 1000 ),
		FILE_APPEND
	);
}, 9999 );
```

```bash
curl -s -o /dev/null https://example.com/
curl -s -o /dev/null https://example.com/de/
tail /wp-content/perf.log
```

Run each URL twice - the _second_ run is the warm-cache number that matters. If your query counts are far above the table above, one of the four layers isn’t doing its job. Check them in order: object cache, OPcache, CDN.

## Related

-   [CDN Cache-Tag Headers](https://perflocale.com/docs/cache-tags/) - per-language CDN purging
-   [Edge Integration](https://perflocale.com/docs/edge-integration/) - Cloudflare Workers / Vercel / Netlify
-   [Performance feature page](https://perflocale.com/features/performance/) - the architectural choices that make PerfLocale fast by default
