One of the less obvious costs of running large-scale proxy infrastructure is bandwidth. Most proxy providers charge based on traffic usage, so repeatedly downloading the same resources can quickly become expensive.
To help reduce those costs, ProxyMapService includes an HTTP response caching feature. When enabled, frequently requested resources can be served directly from the local cache instead of being downloaded through the upstream proxy again. In many scenarios this significantly reduces bandwidth consumption and lowers operating costs.
Enabling the Cache
Caching is disabled by default. To enable it, set Enabled to true in the Cache section of appsettings.json:
"Cache": {
"Enabled": true,
"DbPath": "c:\\temp\\cache\\proxymap.db",
"CacheDir": "c:\\temp\\cache\\files"
}
Configuration options:
| Setting | Description |
|---|---|
Enabled |
Enables or disables caching. |
DbPath |
Path to the SQLite database used to store cache metadata. |
CacheDir |
Directory where cached response bodies are stored. |
What Gets Cached
ProxyMapService only caches responses that meet all of the following conditions:
- The request method is
GET - The response status code is
200 OK - The response contains a valid content length
- The response matches a configured cache rule
- The response is considered cacheable according to the cache policy evaluation process
This approach avoids storing dynamic or non-cacheable content while keeping the cache efficient.
Cache Storage Structure
Cached responses are split into two parts:
Metadata
Metadata is stored in a SQLite table named cache_entries.
CREATE TABLE cache_entries
(
key TEXT PRIMARY KEY,
host TEXT,
url TEXT,
etag TEXT,
cache_control TEXT,
date TEXT,
expires TEXT,
last_modified TEXT,
header_length INTEGER,
content_length INTEGER,
content_type TEXT,
created_at TEXT,
last_access TEXT
)
The table stores information required to evaluate cache validity, including HTTP cache headers such as ETag, Cache-Control, Expires, and Last-Modified.
Response Body
The actual response content is stored as a binary file under CacheDir.
Each cache entry receives a SHA-256 key generated from:
host + url
Example key:
18c97f79e87cdfdd7fd0cce3048cc6643b18ed7b154efe859d9fab09209d012a
Example cache file location:
c:\temp\cache\files\18\18c9\18c97f79e87cdfdd7fd0cce3048cc6643b18ed7b154efe859d9fab09209d012a.bin
The nested directory structure prevents large numbers of files from accumulating in a single folder.
Defining Cache Rules
Caching is controlled through CacheRules configured within a host rule.
Example:
{
"Pattern": "gstatic\\.com$",
"HostPort": 443,
"Action": "Bypass",
"DecryptSSL": true,
"CacheRules": {
"Items": [
{
"Pattern": "/images",
"ContentTypePattern": "image/",
"MaxAge": 60
}
]
}
}
This configuration instructs ProxyMapService to cache:
- Requests sent to hosts matching
gstatic\.com$ - URLs matching
/images - Responses whose
Content-Typematchesimage/ - Responses younger than 60 seconds
HTTPS Traffic Requires SSL/TLS Decryption
Notice the following setting:
"DecryptSSL": true
This is required when caching HTTPS traffic.
Without SSL/TLS decryption, ProxyMapService cannot inspect the request path, response headers, or content type, making cache decisions impossible.
If you are not familiar with SSL/TLS interception in ProxyMapService, see the previous article, SSL/TLS Traffic Decryption in ProxyMapService, which explains the setup process in detail.
Available Cache Rule Options
Cache rules support several filters and cache-control options.
Pattern
Matches the request URL using a regular expression.
{
"Pattern": "/images"
}
Only requests whose URL matches the expression are considered.
AcceptPattern
Matches the request’s Accept header.
{
"AcceptPattern": "image/"
}
This can be useful when the same URL may return different content types depending on client preferences.
ContentTypePattern
Matches the response Content-Type header.
{
"ContentTypePattern": "image/"
}
The response must match this pattern before it can be stored or served from cache.
MaxAge
Defines a fixed lifetime in seconds.
{
"MaxAge": 3600
}
In this example, cached content expires one hour after it is stored.
When MaxAge is specified, ProxyMapService calculates:
CreatedAt + MaxAge
If the current time exceeds that value, the cache entry is treated as expired regardless of any HTTP cache headers.
IgnoreCacheControl
{
"IgnoreCacheControl": true
}
When enabled, ProxyMapService ignores the server’s Cache-Control, Expires, and validation directives.
Only the rule’s MaxAge setting determines whether the cached response can be used.
This can be useful when caching content from servers that provide overly restrictive cache headers.
How Cache Validation Works
When a cached entry is requested, ProxyMapService evaluates whether the entry can still be used.
The evaluation process follows standard HTTP caching semantics.
1. Cache-Control: no-store
Cache-Control: no-store
The response must never be reused.
Result:
Refresh
The content is downloaded again from the origin server.
2. Cache-Control: no-cache
Cache-Control: no-cache
The response may be stored, but it must be revalidated before use.
If the response contains validators such as ETag or Last-Modified:
Revalidate
Otherwise:
Refresh
3. Freshness Lifetime Calculation
ProxyMapService determines freshness using the following priority order.
max-age
Cache-Control: max-age=3600
This has the highest priority.
Expires
If no max-age exists:
Expires: Wed, 25 Jun 2025 12:00:00 GMT
The lifetime is calculated as:
Expires - Date
Heuristic Expiration
If neither max-age nor Expires exists, ProxyMapService falls back to a heuristic.
It calculates approximately 10% of:
Date - Last-Modified
For example, if a resource was last modified 10 days before the response date, the cache lifetime becomes roughly 1 day.
If that information is unavailable, a default lifetime of 10 minutes is used.
Stale-While-Revalidate Support
ProxyMapService also honors the stale-while-revalidate directive.
Example:
Cache-Control: max-age=60, stale-while-revalidate=300
Behavior:
- First 60 seconds → content is considered fresh
- Next 300 seconds → stale content may still be served
- Background revalidation can occur during this period
This reduces latency and unnecessary traffic while keeping content reasonably fresh.
Validation Using ETag and Last-Modified
When cached content becomes stale, ProxyMapService checks whether validation information is available.
Supported validators:
ETag: "abc123"
and
Last-Modified: Wed, 25 Jun 2025 12:00:00 GMT
If either validator exists, the entry can be revalidated instead of fully downloaded again.
This often results in a lightweight HTTP validation request rather than retransferring the entire resource.
Typical Use Cases
Traffic caching is especially effective for:
- Images
- JavaScript bundles
- CSS files
- Fonts
- Static CDN content
- Frequently requested API responses with predictable cache headers
A common pattern is to cache content from CDN providers such as Google Static Content, Cloudflare, or other asset delivery networks.
Final Thoughts
For environments where proxy traffic directly affects operating costs, caching can provide immediate savings. By storing frequently requested resources locally and honoring standard HTTP caching semantics, ProxyMapService reduces bandwidth consumption while still respecting content freshness rules.
In practice, even a small set of well-targeted cache rules for static assets can significantly reduce upstream traffic and improve response times for clients.