Configuring Browser-Side Caching for 1C-Bitrix
Configuring Browser-Side Caching for 1C-Bitrix
A returning visitor loads the page in 4 seconds even though the logs show a TTFB of 200 ms. The waterfall in DevTools explains it: 80 requests to static assets, each returning a 304 Not Modified. Files are served from the browser cache, but the browser still asks the server "has this changed?" Properly set caching headers eliminate these requests entirely.
Browser Cache Model
The browser uses two mechanisms:
Freshness — the browser knows until what point a file is current and does not contact the server at all. Controlled by Cache-Control: max-age and Expires headers.
Validation — the browser asks the server "has the file changed?" using ETag or Last-Modified. The server responds with 304, and the browser uses the cache. Saves bandwidth, but not RTT.
The goal is to maximize Freshness for versioned static assets and use Validation for HTML.
nginx Configuration for Bitrix
server {
# HTML — short cache with revalidation
location ~* \.html?$ {
add_header Cache-Control "no-cache, must-revalidate";
etag on;
}
# CSS, JS with hashes in names (Vite/Webpack) — long cache
location ~* /build/assets/.*\.[a-f0-9]{8,}\.(css|js)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# Static assets without hashes — moderate cache
location ~* \.(css|js)$ {
add_header Cache-Control "public, max-age=604800";
etag on;
}
# Images
location ~* \.(jpg|jpeg|png|gif|webp|avif|ico|svg)$ {
add_header Cache-Control "public, max-age=2592000";
access_log off;
}
# Fonts
location ~* \.(woff|woff2|ttf|otf|eot)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin "*";
access_log off;
}
# Bitrix assets (no hashes)
location ~* ^/bitrix/(js|css|fonts)/ {
add_header Cache-Control "public, max-age=604800";
etag on;
}
}
immutable in Cache-Control — the browser will not check the file even on a manual page refresh (Ctrl+F5 has no effect). Apply only to files with a version hash in the filename.
Asset Versioning in Bitrix
Without versioning, a long cache is dangerous: after a CSS update, users see the old design for a week.
Built-in Bitrix mechanism — the sessid parameter in asset URLs. Not ideal: it changes every session and invalidates the cache more often than needed.
Via .htaccess / nginx map — append a content hash to static asset URLs at deploy time:
# Deploy script: generate versions
for f in /var/www/site/public/css/*.css; do
hash=$(md5sum "$f" | cut -c1-8)
# Create symlink with hash
ln -sf "$(basename $f)" "${f%.css}.${hash}.css"
done
Vite/Webpack — if the frontend is built via Vite (as in this project), hashes in filenames are generated automatically: app-B3vCf7Tf.js. For these files, max-age=31536000, immutable can safely be applied.
Bitrix Cache Invalidation via Query Parameter
For assets without automatic versioning, use a manual parameter in the template:
// In the template's header.php
$version = '2024031301'; // update on each deploy
echo '<link rel="stylesheet" href="/css/custom.css?v=' . $version . '">';
Automate: read the version from a version.txt file updated by CI/CD:
$version = trim(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/version.txt'));
Apache .htaccess
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/html "access plus 0 seconds"
ExpiresByType text/css "access plus 1 week"
ExpiresByType application/javascript "access plus 1 week"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/webp "access plus 1 month"
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\.(css|js)$">
Header set Cache-Control "public, max-age=604800"
</FilesMatch>
</IfModule>
Verification
curl -sI https://site.ru/css/styles.css | grep -i "cache-control\|expires\|etag"
Chrome DevTools → Network → select a resource → Headers: check Cache-Control in Response Headers and from cache / from disk cache in the Size column on a repeat load.







