Virtual-host rewrites run before the directory walk, and <Location> merges after .htaccess (Contexts and Merge Order), so a correct file can still lose:
# In the virtual host
<Location "/downloads/">
Header set Cache-Control "no-store"
</Location>
RewriteEngine On
RewriteRule ^/old-blog/(.*)$ /blog/$1 [R=301,L]
# /var/www/example/.htaccess
Header set Cache-Control "max-age=3600"
RewriteEngine On
RewriteRule ^old-blog/(.*)$ /articles/$1 [R=301,L]
RewriteRule ^old-news/(.*)$ /news/$1 [R=301,L,E=MOVED:1]
Header always set Cache-Control "max-age=86400" env=MOVED/downloads/report.zip 200 no-store /app.php 200 max-age=3600 /old-blog/2026/post.html 301 http://localhost:8111/blog/2026/post.html /old-news/x 301 max-age=86400 http://localhost:8111/news/x
<Location> won, and the virtual host answered /old-blog/ first. app.php sends Cache-Control: private, max-age=60, which Header set replaced under mod_php; under PHP-FPM (PHP-FPM via proxy_fcgi) both values went out, since proxied headers sit in the always table, until the line said Header always set. The last rule caps a redirect's life: RFC 9110 makes 301 and 308 cacheable by default, so a browser may reuse a wrong one without asking again. Test with R=302.
HSTS (HTTPS and HSTS) and cached assets (Cache-Control and ETags) outlast their rules the same way; curl 3,008 -I keeps no cache, so check with it first.