Repository: nginx/kubernetes-ingress

Issue:
Extend Cache Policy to support more configuration parameters
**Is your feature request related to a problem? Please describe.**
Currently, on 5.2.0, Cache Policy doesnt allow users to specify `inactivity` and `use_temp_path` parameters as available for `proxy_cache_path` directive.  This means that users cant set the inactive cache lifetime and are forced to spend disk IO on moving the cache files (e.g. when using a StatefulSet and a separate volume).

Those are the options that were specifically asked by the customers.

Additionally, it's not possible to configure:

 - anything cache manager related in `proxy_cache_path` - `max_size`, `min_free`, `manager_*` which are crucial to maintain the filesystem usage for caches

 - `loader_*` is less widely used, but sometimes tuning can be beneficial in the scenarios where NGINX is restarted and needs to prime the cache info

 - `proxy_cache_key` which would allow users to redefine the caching keys for the cache items

 - `proxy_cache_use_stale` and `proxy_cache_revalidate` are useful to minimize the impact on backends when updating the expired cached items
 
 - `proxy_no_cache` and `proxy_cache_bypass` would allow to bypass the caching for specific requests

 - `proxy_cache_lock*` directives are useful for highly loaded services, eliminating the pressure on backends

**Describe the solution you'd like**
A support for aforementioned items in the Cache Policy

**Describe alternatives you've considered**
Snippets can still be used to implement the desired functionality.



Hints from issue comments:
@thresheek: I've got a confirmation from user that they use the following directives:

```
proxy_cache
proxy_cache_background_update
proxy_cache_key
proxy_cache_lock
proxy_cache_min_uses
proxy_cache_path
proxy_cache_revalidate
proxy_cache_use_stale
proxy_ignore_headers
```

They confirmed they use `inactive` and `use_temp_path` in `proxy_cache_path`, and redefine proxy_cache_key using a mix of nginx-provided built-in variables and variables set up by `map`, too.@haywoodsh: Original design
https://github.com/nginx/kubernetes-ingress/issues/7773#issuecomment-2895040002

PR
https://github.com/nginx/kubernetes-ingress/commit/3aebdfc689ce0b41b86c8e19b4d9d01e73803428@haywoodsh: Possible updated policy included all fields mentioned above:
```
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
  name: my-cache-policy
spec:
  cache:
    cacheZoneName: "mycache"                    # Required - proxy_cache (zone name)
    cacheZoneSize: "10m"                        # Required - proxy_cache_path (keys_zone size)
    allowedCodes: [200, 301, 302]              # Optional - proxy_cache_valid (which HTTP codes to cache)
    allowedMethods: ["GET", "POST"]            # Optional - proxy_cache_methods (which methods to cache)
    time: "10m"                                 # Optional - proxy_cache_valid (default time)
    cachePurgeAllow: ["10.0.0.1","127.0.0.0/24"] # Optional - proxy_cache_purge (NGINX Plus only)
    overrideUpstreamCache: true                 # Optional, false by default - proxy_ignore_headers (Cache-Control, Expires, Set-Cookie, Vary)
    levels: "1:2"                               # Optional - proxy_cache_path (directory levels)

    #new fields required
    inactive: "60m"                             # Optional - proxy_cache_path (inactive parameter)
    useTempPath: false                          # Optional, false by default - proxy_cache_path (use_temp_path=off)
    maxSize: "10g"                              # Optional - proxy_cache_path (max_size)
    minFree: "1g"                               # Optional - proxy_cache_path (min_free)
    managerFiles: "100"                         # Optional - proxy_cache_path (manager_files)
    managerSleep: "50ms"                        # Optional - proxy_cache_path (manager_sleep)
    managerThreshold: "200ms"                   # Optional - proxy_cache_path (manager_threshold)
    cacheKey: "$scheme$request_method$host$request_uri" # Optional - proxy_cache_key (custom cache key)
    cacheUseStale: ["error", "timeout", "updating", "http_500"] # Optional - proxy_cache_use_stale (serve stale conditions)
    cacheRevalidate: true                       # Optional, false by default - proxy_cache_revalidate
    cacheBackgroundUpdate: true                 # Optional, false by default - proxy_cache_background_update
    cacheMinUses: 3                             # Optional - proxy_cache_min_uses (requests before caching)
    cacheLock: true                             # Optional, false by default - proxy_cache_lock
    cacheLockTimeout: "5s"                      # Optional - proxy_cache_lock_timeout
    cacheLockAge: "30s"                         # Optional - proxy_cache_lock_age
    noCacheConditions: ["$cookie_nocache", "$arg_nocache"] # Optional - proxy_no_cache (skip caching conditions)
    cacheBypassConditions: ["$http_authorization"] # Optional - proxy_cache_bypass (bypass cache conditions)
    ignoreHeaders: ["Set-Cookie", "Vary"]       # Optional - proxy_ignore_headers (additional headers to ignore) potentially overlap with overrideUpstreamCache
```@haywoodsh: Updated specs:
```
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
  name: cache-policy
spec:
  cache:
    cacheZoneName: "testcache" # Required
    cacheZoneSize: "15m" # Required
    allowedCodes: ["any"] # Optional ["any"] or [200, 301, ...], "any" cannot be combined with specific codes
    allowedMethods: ["GET", "HEAD", "POST"] # Optional
    time: "30m" # Optional # e.g. "15m", "1h", "2d". Default is "10m"
    overrideUpstreamCache: true # Optional, default is false
    levels: "1:2" # Optional, default is "1:1" , see https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path for more details
    # cachePurgeAllow: [""] # Optional, If set allows cache purging from the specified IPs or CIDR ranges. Nginx Plus only.
    inactive: "60m"                             # Optional - proxy_cache_path (inactive parameter)
    useTempPath: false                          # Optional, false by default - proxy_cache_path (use_temp_path=off)
    maxSize: "10g"                              # Optional - proxy_cache_path (max_size)
    minFree: "1g"                               # Optional - proxy_cache_path (min_free)
    manager:                                    # Optional - proxy_cache_path manager settings
      files: 100                               # manager_files
      sleep: "50ms"                            # manager_sleep
      threshold: "200ms"                       # manager_threshold

    # Advanced cache behavior settings
    cacheKey: "${scheme}${request_method}${host}${request_uri}" # Optional - proxy_cache_key (custom cache key)
    cacheUseStale: [ "error", "timeout", "updating", "http_500" ] # Optional - proxy_cache_use_stale (serve stale conditions)
    cacheRevalidate: true                       # Optional, false by default - proxy_cache_revalidate
    cacheBackgroundUpdate: true                 # Optional, false by default - proxy_cache_background_update
    cacheMinUses: 3                             # Optional - proxy_cache_min_uses (requests before caching)

    # Cache locking settings
    lock:                                       # Optional - proxy_cache_lock settings
      enable: true                             # proxy_cache_lock
      timeout: "5s"                            # proxy_cache_lock_timeout
      age: "30s"                               # proxy_cache_lock_age

    # Conditional caching settings
    conditions:                                 # Optional - cache condition settings
      noCache: [ "$cookie_nocache", "$arg_nocache" ] # proxy_no_cache (skip caching conditions)
      bypass: [ "$http_authorization" ]        # proxy_cache_bypass (bypass cache conditions)
```
Validation considerations:
-  why does size here supports `g` and `G`. only offset does as per the docs https://nginx.org/en/docs/syntax.html
- `lock.enable = true` should be necessary for `lock.timeout` and `lock.age`

Other considerations:
- should `proxy_cache_key` be empty unless provided in the policy? if the directive is not present, the default would be `proxy_cache_key $scheme$proxy_host$uri$is_args$args;` according to the docs
conclusion: use $scheme$proxy_host$request_uri as default
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_key
- `useTempPath` needs `proxy_temp_path` when it's on or omitted. should probably be off by default. consider 
  - enforcing path provided  when `useTempPath=true` with the `tempPath` string field for [proxy_temp_path](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_temp_path) , or
  - use the  `tempPath`  to implicitly toggle`use_temp_path `, or
  **-  hardcode this to `false`**

- `ignoreHeaders` is not necessary, as the variables defined in`overrideUpstreamCache` cover all necessary variables for the cache scenario. `proxy.responseHeaders.Ignore` can be used if customisation is needed
skip this
- what variables should be allowed for `cacheKey`? `uri` and `proxy_host`, etc? 

Solution patch:
diff --git a/config/crd/bases/k8s.nginx.org_policies.yaml b/config/crd/bases/k8s.nginx.org_policies.yaml
index ea9fabca3..e55188b30 100644
--- a/config/crd/bases/k8s.nginx.org_policies.yaml
+++ b/config/crd/bases/k8s.nginx.org_policies.yaml
@@ -140,6 +140,29 @@ spec:
                     x-kubernetes-validations:
                     - message: 'allowed methods must be one of: GET, HEAD, POST'
                       rule: self.all(method, method in ['GET', 'HEAD', 'POST'])
+                  cacheBackgroundUpdate:
+                    default: false
+                    description: |-
+                      CacheBackgroundUpdate allows starting a background subrequest to update an expired cache item (proxy_cache_background_update).
+                      A stale cached response is returned to the client while the cache is being updated.
+                    type: boolean
+                  cacheKey:
+                    description: |-
+                      CacheKey defines a key for caching (proxy_cache_key).
+                      By default, close to "$scheme$proxy_host$uri$is_args$args".
+                      Must not contain command execution patterns: $(, `, ;, &&, ||
+                    maxLength: 1024
+                    type: string
+                    x-kubernetes-validations:
+                    - message: 'cache key must not contain command execution patterns:
+                        $(, `, ;, &&, ||'
+                      rule: '!self.contains(''$('') && !self.contains(''`'') && !self.contains('';'')
+                        && !self.contains(''&&'') && !self.contains(''||'')'
+                  cacheMinUses:
+                    description: CacheMinUses sets the number of requests after which
+                      the response will be cached (proxy_cache_min_uses).
+                    minimum: 1
+                    type: integer
                   cachePurgeAllow:
                     description: |-
                       CachePurgeAllow defines IP addresses or CIDR blocks allowed to purge cache.
@@ -149,6 +172,20 @@ spec:
                     items:
                       type: string
                     type: array
+                  cacheRevalidate:
+                    default: false
+                    description: |-
+                      CacheRevalidate enables revalidation of expired cache items using conditional requests (proxy_cache_revalidate).
+                      Uses "If-Modified-Since" and "If-None-Match" header fields.
+                    type: boolean
+                  cacheUseStale:
+                    description: |-
+                      CacheUseStale determines in which cases a stale cached response can be used (proxy_cache_use_stale).
+                      Valid parameters: error, timeout, invalid_header, updating, http_500, http_502, http_503, http_504, http_403, http_404, http_429, off.
+                    items:
+                      type: string
+                    maxItems: 11
+                    type: array
                   cacheZoneName:
                     description: |-
                       CacheZoneName defines the name of the cache zone. Must start with a lowercase letter,
@@ -159,9 +196,34 @@ spec:
                   cacheZoneSize:
                     description: |-
                       CacheZoneSize defines the size of the cache zone. Must be a number followed by a size unit:
-                      'k' for kilobytes, 'm' for megabytes, or 'g' for gigabytes.
+                      'k' or 'K' for kilobytes, 'm' or 'M' for megabytes, or 'g' or 'G' for gigabytes.
                       Examples: "10m", "1g", "512k".
-                    pattern: ^[0-9]+[kmg]$
+                    pattern: ^[0-9]+[kmgKMG]$
+                    type: string
+                  conditions:
+                    description: Conditions defines when responses should not be cached
+                      or taken from cache.
+                    properties:
+                      bypass:
+                        description: |-
+                          Bypass defines conditions under which the response will not be taken from a cache (proxy_cache_bypass).
+                          If at least one value of the string parameters is not empty and is not equal to "0" then the response will not be taken from the cache.
+                        items:
+                          type: string
+                        type: array
+                      noCache:
+                        description: |-
+                          NoCache defines conditions under which the response will not be saved to a cache (proxy_no_cache).
+                          If at least one value of the string parameters is not empty and is not equal to "0" then the response will not be saved.
+                        items:
+                          type: string
+                        type: array
+                    type: object
+                  inactive:
+                    description: |-
+                      Inactive sets the time after which cached data that are not accessed get removed from the cache (inactive parameter).
+                      By default, inactive is set to 10 minutes.
+                    pattern: ^[0-9]+[smhd]$
                     type: string
                   levels:
                     description: |-
@@ -172,6 +234,67 @@ spec:
                       Invalid: "3:1", "1:3", "1:2:3".
                     pattern: ^[12](?::[12]){0,2}$
                     type: string
+                  lock:
+                    description: Lock configures cache locking to prevent multiple
+                      identical requests from populating the same cache element simultaneously.
+                    properties:
+                      age:
+                        description: |-
+                          Age sets the maximum time a cache lock can be held (proxy_cache_lock_age).
+                          If the last request passed to the proxied server for populating a new cache element has not completed for the specified time, one more request may be passed.
+                        pattern: ^[0-9]+[smhd]$
+                        type: string
+                      enable:
+                        default: false
+                        description: |-
+                          Enable sets whether cache locking is enabled (proxy_cache_lock).
+                          When enabled, only one request at a time will be allowed to populate a new cache element according to the proxy_cache_key.
+                        type: boolean
+                      timeout:
+                        description: |-
+                          Timeout sets a timeout for proxy_cache_lock.
+                          When the time expires, the request will be passed to the proxied server, however, the response will not be cached.
+                        pattern: ^[0-9]+[smhd]$
+                        type: string
+                    type: object
+                    x-kubernetes-validations:
+                    - message: timeout or age require enable=true
+                      rule: (!has(self.timeout) && !has(self.age)) || self.enable
+                  manager:
+                    description: Manager configures the cache manager process parameters
+                      (manager_files, manager_sleep, manager_threshold).
+                    properties:
+                      files:
+                        description: |-
+                          Files sets the maximum number of files that will be deleted in one iteration by the cache manager.
+                          During one iteration no more than manager_files items are deleted (by default, 100).
+                        minimum: 1
+                        type: integer
+                      sleep:
+                        description: |-
+                          Sleep sets the pause between cache manager iterations.
+                          Between iterations, a pause configured by manager_sleep (by default, 50 milliseconds) is made.
+                        pattern: ^[0-9]+[mu]?s$
+                        type: string
+                      threshold:
+                        description: |-
+                          Threshold sets the maximum duration of one cache manager iteration.
+                          The duration of one iteration is limited by manager_threshold (by default, 200 milliseconds).
+                        pattern: ^[0-9]+[mu]?s$
+                        type: string
+                    type: object
+                  maxSize:
+                    description: |-
+                      MaxSize sets the maximum cache size (max_size parameter).
+                      When the size is exceeded, the cache manager removes the least recently used data.
+                    pattern: ^[0-9]+[kmgKMG]$
+                    type: string
+                  minFree:
+                    description: |-
+                      MinFree sets the minimum amount of free space required on the file system with cache (min_free parameter).
+                      When there is not enough free space, the cache manager removes the least recently used data.
+                    pattern: ^[0-9]+[kmgKMG]$
+                    type: string
                   overrideUpstreamCache:
                     default: false
                     description: |-
@@ -188,6 +311,13 @@ spec:
                       Examples: "30s", "5m", "1h", "2d".
                     pattern: ^[0-9]+[smhd]$
                     type: string
+                  useTempPath:
+                    default: false
+                    description: |-
+                      UseTempPath controls whether temporary files and the cache are put on different file systems (use_temp_path parameter).
+                      If set to false, temporary files will be put directly in the cache directory (use_temp_path=off).
+                      Default: false (use_temp_path=off, which puts temp files directly in cache directory for better performance).
+                    type: boolean
                 required:
                 - cacheZoneName
                 - cacheZoneSize
diff --git a/deploy/crds.yaml b/deploy/crds.yaml
index dff46a8a9..93fc92efa 100644
--- a/deploy/crds.yaml
+++ b/deploy/crds.yaml
@@ -311,6 +311,29 @@ spec:
                     x-kubernetes-validations:
                     - message: 'allowed methods must be one of: GET, HEAD, POST'
                       rule: self.all(method, method in ['GET', 'HEAD', 'POST'])
+                  cacheBackgroundUpdate:
+                    default: false
+                    description: |-
+                      CacheBackgroundUpdate allows starting a background subrequest to update an expired cache item (proxy_cache_background_update).
+                      A stale cached response is returned to the client while the cache is being updated.
+                    type: boolean
+                  cacheKey:
+                    description: |-
+                      CacheKey defines a key for caching (proxy_cache_key).
+                      By default, close to "$scheme$proxy_host$uri$is_args$args".
+                      Must not contain command execution patterns: $(, `, ;, &&, ||
+                    maxLength: 1024
+                    type: string
+                    x-kubernetes-validations:
+                    - message: 'cache key must not contain command execution patterns:
+                        $(, `, ;, &&, ||'
+                      rule: '!self.contains(''$('') && !self.contains(''`'') && !self.contains('';'')
+                        && !self.contains(''&&'') && !self.contains(''||'')'
+                  cacheMinUses:
+                    description: CacheMinUses sets the number of requests after which
+                      the response will be cached (proxy_cache_min_uses).
+                    minimum: 1
+                    type: integer
                   cachePurgeAllow:
                     description: |-
                       CachePurgeAllow defines IP addresses or CIDR blocks allowed to purge cache.
@@ -320,6 +343,20 @@ spec:
                     items:
                       type: string
                     type: array
+                  cacheRevalidate:
+                    default: false
+                    description: |-
+                      CacheRevalidate enables revalidation of expired cache items using conditional requests (proxy_cache_revalidate).
+                      Uses "If-Modified-Since" and "If-None-Match" header fields.
+                    type: boolean
+                  cacheUseStale:
+                    description: |-
+                      CacheUseStale determines in which cases a stale cached response can be used (proxy_cache_use_stale).
+                      Valid parameters: error, timeout, invalid_header, updating, http_500, http_502, http_503, http_504, http_403, http_404, http_429, off.
+                    items:
+                      type: string
+                    maxItems: 11
+                    type: array
                   cacheZoneName:
                     description: |-
                       CacheZoneName defines the name of the cache zone. Must start with a lowercase letter,
@@ -330,9 +367,34 @@ spec:
                   cacheZoneSize:
                     description: |-
                       CacheZoneSize defines the size of the cache zone. Must be a number followed by a size unit:
-                      'k' for kilobytes, 'm' for megabytes, or 'g' for gigabytes.
+                      'k' or 'K' for kilobytes, 'm' or 'M' for megabytes, or 'g' or 'G' for gigabytes.
                       Examples: "10m", "1g", "512k".
-                    pattern: ^[0-9]+[kmg]$
+                    pattern: ^[0-9]+[kmgKMG]$
+                    type: string
+                  conditions:
+                    description: Conditions defines when responses should not be cached
+                      or taken from cache.
+                    properties:
+                      bypass:
+                        description: |-
+                          Bypass defines conditions under which the response will not be taken from a cache (proxy_cache_bypass).
+                          If at least one value of the string parameters is not empty and is not equal to "0" then the response will not be taken from the cache.
+                        items:
+                          type: string
+                        type: array
+                      noCache:
+                        description: |-
+                          NoCache defines conditions under which the response will not be saved to a cache (proxy_no_cache).
+                          If at least one value of the string parameters is not empty and is not equal to "0" then the response will not be saved.
+                        items:
+                          type: string
+                        type: array
+                    type: object
+                  inactive:
+                    description: |-
+                      Inactive sets the time after which cached data that are not accessed get removed from the cache (inactive parameter).
+                      By default, inactive is set to 10 minutes.
+                    pattern: ^[0-9]+[smhd]$
                     type: string
                   levels:
                     description: |-
@@ -343,6 +405,67 @@ spec:
                       Invalid: "3:1", "1:3", "1:2:3".
                     pattern: ^[12](?::[12]){0,2}$
                     type: string
+                  lock:
+                    description: Lock configures cache locking to prevent multiple
+                      identical requests from populating the same cache element simultaneously.
+                    properties:
+                      age:
+                        description: |-
+                          Age sets the maximum time a cache lock can be held (proxy_cache_lock_age).
+                          If the last request passed to the proxied server for populating a new cache element has not completed for the specified time, one more request may be passed.
+                        pattern: ^[0-9]+[smhd]$
+                        type: string
+                      enable:
+                        default: false
+                        description: |-
+                          Enable sets whether cache locking is enabled (proxy_cache_lock).
+                          When enabled, only one request at a time will be allowed to populate a new cache element according to the proxy_cache_key.
+                        type: boolean
+                      timeout:
+                        description: |-
+                          Timeout sets a timeout for proxy_cache_lock.
+                          When the time expires, the request will be passed to the proxied server, however, the response will not be cached.
+                        pattern: ^[0-9]+[smhd]$
+                        type: string
+                    type: object
+                    x-kubernetes-validations:
+                    - message: timeout or age require enable=true
+                      rule: (!has(self.timeout) && !has(self.age)) || self.enable
+                  manager:
+                    description: Manager configures the cache manager process parameters
+                      (manager_files, manager_sleep, manager_threshold).
+                    properties:
+                      files:
+                        description: |-
+                          Files sets the maximum number of files that will be deleted in one iteration by the cache manager.
+                          During one iteration no more than manager_files items are deleted (by default, 100).
+                        minimum: 1
+                        type: integer
+                      sleep:
+                        description: |-
+                          Sleep sets the pause between cache manager iterations.
+                          Between iterations, a pause configured by manager_sleep (by default, 50 milliseconds) is made.
+                        pattern: ^[0-9]+[mu]?s$
+                        type: string
+                      threshold:
+                        description: |-
+                          Threshold sets the maximum duration of one cache manager iteration.
+                          The duration of one iteration is limited by manager_threshold (by default, 200 milliseconds).
+                        pattern: ^[0-9]+[mu]?s$
+                        type: string
+                    type: object
+                  maxSize:
+                    description: |-
+                      MaxSize sets the maximum cache size (max_size parameter).
+                      When the size is exceeded, the cache manager removes the least recently used data.
+                    pattern: ^[0-9]+[kmgKMG]$
+                    type: string
+                  minFree:
+                    description: |-
+                      MinFree sets the minimum amount of free space required on the file system with cache (min_free parameter).
+                      When there is not enough free space, the cache manager removes the least recently used data.
+                    pattern: ^[0-9]+[kmgKMG]$
+                    type: string
                   overrideUpstreamCache:
                     default: false
                     description: |-
@@ -359,6 +482,13 @@ spec:
                       Examples: "30s", "5m", "1h", "2d".
                     pattern: ^[0-9]+[smhd]$
                     type: string
+                  useTempPath:
+                    default: false
+                    description: |-
+                      UseTempPath controls whether temporary files and the cache are put on different file systems (use_temp_path parameter).
+                      If set to false, temporary files will be put directly in the cache directory (use_temp_path=off).
+                      Default: false (use_temp_path=off, which puts temp files directly in cache directory for better performance).
+                    type: boolean
                 required:
                 - cacheZoneName
                 - cacheZoneSize
diff --git a/docs/crd/k8s.nginx.org_policies.md b/docs/crd/k8s.nginx.org_policies.md
index 7df0e5b22..80f846a0b 100644
--- a/docs/crd/k8s.nginx.org_policies.md
+++ b/docs/crd/k8s.nginx.org_policies.md
@@ -29,12 +29,32 @@ The `.spec` object supports the following fields:
 | `cache` | `object` | The Cache Key defines a cache policy for proxy caching |
 | `cache.allowedCodes` | `array` | AllowedCodes defines which HTTP response codes should be cached. Accepts either: - The string "any" to cache all response codes (must be the only element) - A list of HTTP status codes as integers (100-599) Examples: ["any"], [200, 301, 404], [200]. Invalid: ["any", 200] (cannot mix "any" with specific codes). |
 | `cache.allowedMethods` | `array[string]` | AllowedMethods defines which HTTP methods should be cached. Only "GET", "HEAD", and "POST" are supported by NGINX proxy_cache_methods directive. GET and HEAD are always cached by default even if not specified. Maximum of 3 items allowed. Examples: ["GET"], ["GET", "HEAD", "POST"]. Invalid methods: PUT, DELETE, PATCH, etc. |
+| `cache.cacheBackgroundUpdate` | `boolean` | CacheBackgroundUpdate allows starting a background subrequest to update an expired cache item (proxy_cache_background_update). A stale cached response is returned to the client while the cache is being updated. |
+| `cache.cacheKey` | `string` | CacheKey defines a key for caching (proxy_cache_key). By default, close to "$scheme$proxy_host$uri$is_args$args". Must not contain command execution patterns: $(, `, ;, &&, || |
+| `cache.cacheMinUses` | `integer` | CacheMinUses sets the number of requests after which the response will be cached (proxy_cache_min_uses). |
 | `cache.cachePurgeAllow` | `array[string]` | CachePurgeAllow defines IP addresses or CIDR blocks allowed to purge cache. This feature is only available in NGINX Plus. Examples: ["192.168.1.100", "10.0.0.0/8", "::1"]. Invalid in NGINX OSS (will be ignored). |
+| `cache.cacheRevalidate` | `boolean` | CacheRevalidate enables revalidation of expired cache items using conditional requests (proxy_cache_revalidate). Uses "If-Modified-Since" and "If-None-Match" header fields. |
+| `cache.cacheUseStale` | `array[string]` | CacheUseStale determines in which cases a stale cached response can be used (proxy_cache_use_stale). Valid parameters: error, timeout, invalid_header, updating, http_500, http_502, http_503, http_504, http_403, http_404, http_429, off. |
 | `cache.cacheZoneName` | `string` | CacheZoneName defines the name of the cache zone. Must start with a lowercase letter, followed by alphanumeric characters or underscores, and end with an alphanumeric character. Single lowercase letters are also allowed. Examples: "cache", "my_cache", "cache1". |
-| `cache.cacheZoneSize` | `string` | CacheZoneSize defines the size of the cache zone. Must be a number followed by a size unit: 'k' for kilobytes, 'm' for megabytes, or 'g' for gigabytes. Examples: "10m", "1g", "512k". |
+| `cache.cacheZoneSize` | `string` | CacheZoneSize defines the size of the cache zone. Must be a number followed by a size unit: 'k' or 'K' for kilobytes, 'm' or 'M' for megabytes, or 'g' or 'G' for gigabytes. Examples: "10m", "1g", "512k". |
+| `cache.conditions` | `object` | Conditions defines when responses should not be cached or taken from cache. |
+| `cache.conditions.bypass` | `array[string]` | Bypass defines conditions under which the response will not be taken from a cache (proxy_cache_bypass). If at least one value of the string parameters is not empty and is not equal to "0" then the response will not be taken from the cache. |
+| `cache.conditions.noCache` | `array[string]` | NoCache defines conditions under which the response will not be saved to a cache (proxy_no_cache). If at least one value of the string parameters is not empty and is not equal to "0" then the response will not be saved. |
+| `cache.inactive` | `string` | Inactive sets the time after which cached data that are not accessed get removed from the cache (inactive parameter). By default, inactive is set to 10 minutes. |
 | `cache.levels` | `string` | Levels defines the cache directory hierarchy levels for storing cached files. Must be in format "X:Y" or "X:Y:Z" where X, Y, Z are either 1 or 2. This controls the number of subdirectory levels and their name lengths. Examples: "1:2", "2:2", "1:2:2". Invalid: "3:1", "1:3", "1:2:3". |
+| `cache.lock` | `object` | Lock configures cache locking to prevent multiple identical requests from populating the same cache element simultaneously. |
+| `cache.lock.age` | `string` | Age sets the maximum time a cache lock can be held (proxy_cache_lock_age). If the last request passed to the proxied server for populating a new cache element has not completed for the specified time, one more request may be passed. |
+| `cache.lock.enable` | `boolean` | Enable sets whether cache locking is enabled (proxy_cache_lock). When enabled, only one request at a time will be allowed to populate a new cache element according to the proxy_cache_key. |
+| `cache.lock.timeout` | `string` | Timeout sets a timeout for proxy_cache_lock. When the time expires, the request will be passed to the proxied server, however, the response will not be cached. |
+| `cache.manager` | `object` | Manager configures the cache manager process parameters (manager_files, manager_sleep, manager_threshold). |
+| `cache.manager.files` | `integer` | Files sets the maximum number of files that will be deleted in one iteration by the cache manager. During one iteration no more than manager_files items are deleted (by default, 100). |
+| `cache.manager.sleep` | `string` | Sleep sets the pause between cache manager iterations. Between iterations, a pause configured by manager_sleep (by default, 50 milliseconds) is made. |
+| `cache.manager.threshold` | `string` | Threshold sets the maximum duration of one cache manager iteration. The duration of one iteration is limited by manager_threshold (by default, 200 milliseconds). |
+| `cache.maxSize` | `string` | MaxSize sets the maximum cache size (max_size parameter). When the size is exceeded, the cache manager removes the least recently used data. |
+| `cache.minFree` | `string` | MinFree sets the minimum amount of free space required on the file system with cache (min_free parameter). When there is not enough free space, the cache manager removes the least recently used data. |
 | `cache.overrideUpstreamCache` | `boolean` | OverrideUpstreamCache controls whether to override upstream cache headers (using proxy_ignore_headers directive). When true, NGINX will ignore cache-related headers from upstream servers like Cache-Control, Expires, etc. Default: false. |
 | `cache.time` | `string` | Time defines the default cache time. Required when allowedCodes is specified. Must be a number followed by a time unit: 's' for seconds, 'm' for minutes, 'h' for hours, 'd' for days. Examples: "30s", "5m", "1h", "2d". |
+| `cache.useTempPath` | `boolean` | UseTempPath controls whether temporary files and the cache are put on different file systems (use_temp_path parameter). If set to false, temporary files will be put directly in the cache directory (use_temp_path=off). Default: false (use_temp_path=off, which puts temp files directly in cache directory for better performance). |
 | `egressMTLS` | `object` | The EgressMTLS policy configures upstreams authentication and certificate verification. |
 | `egressMTLS.ciphers` | `string` | Specifies the enabled ciphers for requests to an upstream HTTPS server. The default is DEFAULT. |
 | `egressMTLS.protocols` | `string` | Specifies the protocols for requests to an upstream HTTPS server. The default is TLSv1 TLSv1.1 TLSv1.2. |
diff --git a/examples/custom-resources/cache-policy/cache.yaml b/examples/custom-resources/cache-policy/cache.yaml
index 7f3370dd5..499a181df 100644
--- a/examples/custom-resources/cache-policy/cache.yaml
+++ b/examples/custom-resources/cache-policy/cache.yaml
@@ -9,6 +9,32 @@ spec:
     allowedCodes: ["any"] # Optional ["any"] or [200, 301, ...], "any" cannot be combined with specific codes
     allowedMethods: ["GET", "HEAD", "POST"] # Optional
     time: "30m" # Optional # e.g. "15m", "1h", "2d". Default is "10m"
-    overrideUpstreamCache: true # Optional, default is false
+    overrideUpstreamCache: true # Optional, default is false - whether to respect upstream cache-control headers (Cache-Control Expires Set-Cookie Vary X-Accel-Expires)
     # levels: "1:2" # Optional, default is "1:1" , see https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path for more details
     # cachePurgeAllow: [""] # Optional, If set allows cache purging from the specified IPs or CIDR ranges. Nginx Plus only.
+    inactive: "60m"                             # Optional - proxy_cache_path (inactive parameter)
+    useTempPath: false                          # Optional, false by default - proxy_cache_path (use_temp_path=off)
+    maxSize: "10g"                              # Optional - proxy_cache_path (max_size)
+    minFree: "1g"                               # Optional - proxy_cache_path (min_free)
+    manager:                                    # Optional - proxy_cache_path manager settings
+      files: 100                               # manager_files
+      sleep: "50ms"                            # manager_sleep
+      threshold: "200ms"                       # manager_threshold
+
+    # Advanced cache behavior settings
+    cacheKey: "$scheme$host$request_uri$request_method" # Optional - proxy_cache_key (custom cache key)
+    cacheUseStale: [ "error", "timeout", "updating", "http_500" ] # Optional - proxy_cache_use_stale (serve stale conditions)
+    cacheRevalidate: true                       # Optional, false by default - proxy_cache_revalidate
+    cacheBackgroundUpdate: true                 # Optional, false by default - proxy_cache_background_update
+    cacheMinUses: 3                             # Optional - proxy_cache_min_uses (requests before caching)
+
+    # Cache locking settings
+    lock:                                       # Optional - proxy_cache_lock settings
+      enable: true                             # proxy_cache_lock
+      timeout: "5s"                            # proxy_cache_lock_timeout
+      age: "30s"                               # proxy_cache_lock_age
+
+    # Conditional caching settings
+    conditions:                                 # Optional - cache condition settings
+      noCache: [ "$cookie_nocache", "$arg_nocache" ] # proxy_no_cache (skip caching conditions)
+      bypass: [ "$http_authorization" ]        # proxy_cache_bypass (bypass cache conditions)
diff --git a/internal/configs/version2/__snapshots__/templates_test.snap b/internal/configs/version2/__snapshots__/templates_test.snap
index d6bb854c8..11b32b335 100644
--- a/internal/configs/version2/__snapshots__/templates_test.snap
+++ b/internal/configs/version2/__snapshots__/templates_test.snap
@@ -1086,8 +1086,8 @@ upstream test-upstream {
     server 10.0.0.20:8001 max_fails=0 fail_timeout= max_conns=0;
 }
 
-proxy_cache_path /var/cache/nginx/test_cache_full_advanced levels=2:2 keys_zone=test_cache_full_advanced:50m;
-proxy_cache_path /var/cache/nginx/test_cache_location_location_cache keys_zone=test_cache_location_location_cache:20m;
+proxy_cache_path /var/cache/nginx/test_cache_full_advanced levels=2:2 keys_zone=test_cache_full_advanced:50m use_temp_path=off;
+proxy_cache_path /var/cache/nginx/test_cache_location_location_cache keys_zone=test_cache_location_location_cache:20m use_temp_path=off;
 geo $purge_allowed_test_cache_full_advanced {
     default 0;
     127.0.0.1 1;
@@ -1165,8 +1165,8 @@ upstream test-upstream {zone test-upstream ;
     server 10.0.0.20:8001 max_fails=0 fail_timeout= max_conns=0;
 }
 
-proxy_cache_path /var/cache/nginx/test_cache_basic_cache levels=1:2 keys_zone=test_cache_basic_cache:10m;
-proxy_cache_path /var/cache/nginx/test_cache_location_simple_cache keys_zone=test_cache_location_simple_cache:5m;
+proxy_cache_path /var/cache/nginx/test_cache_basic_cache levels=1:2 keys_zone=test_cache_basic_cache:10m use_temp_path=off;
+proxy_cache_path /var/cache/nginx/test_cache_location_simple_cache keys_zone=test_cache_location_simple_cache:5m use_temp_path=off;
 server {
     listen 80;
     listen [::]:80;
@@ -4020,3 +4020,72 @@ server {
 }
 
 ---
+
+[TestExecuteVirtualServerTemplateWithExtendedCachePolicy - 1]
+
+upstream extended-upstream {
+    zone extended-upstream ;
+    server 10.0.0.50:8001 max_fails=0 fail_timeout= max_conns=0;
+}
+
+proxy_cache_path /var/cache/nginx/extended_cache_zone levels=2:2 keys_zone=extended_cache_zone:100m inactive=7d max_size=2g manager_files=500 manager_sleep=200ms manager_threshold=1s use_temp_path=off;
+
+server {
+    listen 80;
+    listen [::]:80;
+
+
+    server_name extended.example.com;
+    status_zone extended.example.com;
+    set $resource_type "virtualserver";
+    set $resource_name "";
+    set $resource_namespace "";
+
+    server_tokens "off";
+    # Server-level cache configuration
+    proxy_cache extended_cache_zone;
+    proxy_cache_key $scheme$host$request_uri$args;
+    proxy_cache_valid 200 1h;
+    proxy_cache_valid 404 10m;
+    proxy_cache_valid any 5m;
+    proxy_cache_use_stale error timeout updating;
+    proxy_cache_revalidate on;
+    proxy_cache_background_update on;
+    proxy_cache_min_uses 3;
+    proxy_cache_lock on;
+    proxy_cache_lock_timeout 60s;
+    proxy_no_cache $cookie_admin;
+    proxy_cache_bypass $http_cache_control;
+
+    
+
+    
+    location /api {
+        set $service "";
+        status_zone "";
+
+        
+        set $default_connection_header close;
+        proxy_connect_timeout ;
+        proxy_read_timeout ;
+        proxy_send_timeout ;
+        client_max_body_size ;
+
+        proxy_buffering off;
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection $vs_connection_header;
+        proxy_pass_request_headers off;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Host $host;
+        proxy_set_header X-Forwarded-Port $server_port;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        proxy_pass http://extended-upstream;
+        proxy_next_upstream ;
+        proxy_next_upstream_timeout ;
+        proxy_next_upstream_tries 0;
+    }
+}
+
+---
diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go
index eaf6d873f..16f50128d 100644
--- a/internal/configs/version2/http.go
+++ b/internal/configs/version2/http.go
@@ -495,20 +495,71 @@ type Variable struct {
 
 // CacheZone defines a proxy cache zone configuration.
 type CacheZone struct {
-	Name   string
-	Size   string
-	Path   string
-	Levels string // Optional. Directory hierarchy for cache files (e.g., "1:2", "2:2", "1:2:2")
+	Name             string
+	Size             string
+	Path             string
+	Levels           string // Optional. Directory hierarchy for cache files (e.g., "1:2", "2:2", "1:2:2")
+	Inactive         string // Optional. Time after which inactive cached data is removed
+	UseTempPath      bool   // Optional. Whether to use temporary path (use_temp_path=off when false)
+	MaxSize          string // Optional. Maximum size of the cache
+	MinFree          string // Optional. Minimum free space required
+	ManagerFiles     *int
+	ManagerSleep     string
+	ManagerThreshold string
 }
 
 // Cache defines cache configuration for locations.
 type Cache struct {
-	ZoneName              string
-	ZoneSize              string
-	Time                  string
-	Valid                 map[string]string // map for codes to time
-	AllowedMethods        []string          // HTTP methods allowed for caching based on proxy_cache_methods
-	CachePurgeAllow       []string          // IPs/CIDRs allowed to purge cache
-	OverrideUpstreamCache bool              // Controls whether to override upstream cache headers
-	Levels                string            // Optional. Directory hierarchy for cache files (e.g., "1:2", "2:2", "1:2:2")
+	// proxy_cache directive
+	ZoneName string // Required. The name of the cache zone
+	ZoneSize string // Required. The size of the cache zone
+
+	// proxy_cache_path directive
+	Levels           string // Optional. Directory hierarchy for cache files (e.g., "1:2", "2:2", "1:2:2")
+	Inactive         string // Optional. Time after which inactive cached data is removed
+	UseTempPath      bool   // Optional. Whether to use temporary path (use_temp_path=off when false)
+	MaxSize          string // Optional. Maximum size of the cache
+	MinFree          string // Optional. Minimum free space required
+	ManagerFiles     *int   // Optional. Number of files manager can handle
+	ManagerSleep     string // Optional. Sleep time between manager runs
+	ManagerThreshold string // Optional. Manager threshold for cache operations
+
+	// proxy_cache_key directive
+	CacheKey string // Optional. Custom cache key
+
+	// proxy_ignore_headers directive
+	OverrideUpstreamCache bool // Controls whether to override upstream cache headers
+
+	// proxy_cache_valid directive
+	Time  string            // Optional. Default cache validity time
+	Valid map[string]string // Optional. Cache validity map for codes to time
+
+	// proxy_cache_methods directive
+	AllowedMethods []string // Optional. HTTP methods to cache
+
+	// proxy_cache_use_stale directive
+	CacheUseStale []string // Optional. Conditions under which stale cached responses may be served
+
+	// proxy_cache_revalidate directive
+	CacheRevalidate bool // Optional. Enables revalidation of expired cache items
+
+	// proxy_cache_background_update directive
+	CacheBackgroundUpdate bool // Optional. Enables background updating of expired items while serving stale content
+
+	// proxy_cache_min_uses directive
+	CacheMinUses *int // Optional. Minimum number of uses before a response is cached
+
+	// proxy_cache_purge directive
+	CachePurgeAllow []string // Optional. IPs/CIDRs allowed to purge cache (Plus only)
+
+	// proxy_cache_lock directive
+	CacheLock        bool   // Optional. Whether to enable cache locking
+	CacheLockTimeout string // Optional. Timeout for cache lock
+	CacheLockAge     string // Optional. Age for cache lock
+
+	// proxy_no_cache directive
+	NoCacheConditions []string // Optional. Conditions under which responses should not be cached
+
+	// proxy_cache_bypass directive
+	CacheBypassConditions []string // Optional. Conditions under which cache should be bypassed for requests
 }
diff --git a/internal/configs/version2/nginx-plus.virtualserver.tmpl b/internal/configs/version2/nginx-plus.virtualserver.tmpl
index 50426f271..4af779672 100644
--- a/internal/configs/version2/nginx-plus.virtualserver.tmpl
+++ b/internal/configs/version2/nginx-plus.virtualserver.tmpl
@@ -71,7 +71,7 @@ limit_req_zone {{ $z.Key }} zone={{ $z.ZoneName }}:{{ $z.ZoneSize }} rate={{ $z.
 {{- end }}
 
 {{- range $c := .CacheZones }}
-proxy_cache_path {{ $c.Path }}{{ if $c.Levels }} levels={{ $c.Levels }}{{ end }} keys_zone={{ $c.Name }}:{{ $c.Size }};
+proxy_cache_path {{ $c.Path }}{{ if $c.Levels }} levels={{ $c.Levels }}{{ end }} keys_zone={{ $c.Name }}:{{ $c.Size }}{{ if $c.Inactive }} inactive={{ $c.Inactive }}{{ end }}{{ if $c.MaxSize }} max_size={{ $c.MaxSize }}{{ end }}{{ if $c.MinFree }} min_free={{ $c.MinFree }}{{ end }}{{ if $c.ManagerFiles }} manager_files={{ $c.ManagerFiles }}{{ end }}{{ if $c.ManagerSleep }} manager_sleep={{ $c.ManagerSleep }}{{ end }}{{ if $c.ManagerThreshold }} manager_threshold={{ $c.ManagerThreshold }}{{ end }}{{ if not $c.UseTempPath }} use_temp_path=off{{ end }};
 {{- end }}
 
 {{- range $m := .StatusMatches }}
@@ -221,7 +221,7 @@ server {
     {{- with $s.Cache }}
     # Server-level cache configuration
     proxy_cache {{ $s.Cache.ZoneName }};
-    proxy_cache_key $scheme$proxy_host$request_uri;
+    proxy_cache_key {{ $s.Cache.CacheKey }};
             {{- if $s.Cache.OverrideUpstreamCache }}
     proxy_ignore_headers Cache-Control Expires Set-Cookie Vary X-Accel-Expires;
             {{- end }}
@@ -233,6 +233,33 @@ server {
             {{- end }}
             {{- if $s.Cache.AllowedMethods }}
     proxy_cache_methods{{ range $s.Cache.AllowedMethods }} {{ . }}{{ end }};
+            {{- end }}
+            {{- if $s.Cache.CacheUseStale }}
+    proxy_cache_use_stale{{ range $s.Cache.CacheUseStale }} {{ . }}{{ end }};
+            {{- end }}
+            {{- if $s.Cache.CacheRevalidate }}
+    proxy_cache_revalidate on;
+            {{- end }}
+            {{- if $s.Cache.CacheBackgroundUpdate }}
+    proxy_cache_background_update on;
+            {{- end }}
+            {{- if $s.Cache.CacheMinUses }}
+    proxy_cache_min_uses {{ $s.Cache.CacheMinUses }};
+            {{- end }}
+            {{- if $s.Cache.CacheLock }}
+    proxy_cache_lock on;
+            {{- end }}
+            {{- if $s.Cache.CacheLockTimeout }}
+    proxy_cache_lock_timeout {{ $s.Cache.CacheLockTimeout }};
+            {{- end }}
+            {{- if $s.Cache.CacheLockAge }}
+    proxy_cache_lock_age {{ $s.Cache.CacheLockAge }};
+            {{- end }}
+            {{- if $s.Cache.NoCacheConditions }}
+    proxy_no_cache{{ range $s.Cache.NoCacheConditions }} {{ . }}{{ end }};
+            {{- end }}
+            {{- if $s.Cache.CacheBypassConditions }}
+    proxy_cache_bypass{{ range $s.Cache.CacheBypassConditions }} {{ . }}{{ end }};
             {{- end }}
             {{- if gt (len $s.Cache.CachePurgeAllow) 0 }}
     proxy_cache_purge $cache_purge_{{ replaceAll $s.Cache.ZoneName "-" "_" }};
@@ -746,7 +773,7 @@ server {
 
         {{- with $l.Cache }}
         proxy_cache {{ $l.Cache.ZoneName }};
-        proxy_cache_key $scheme$proxy_host$request_uri;
+        proxy_cache_key {{ $l.Cache.CacheKey }};
                 {{- if $l.Cache.OverrideUpstreamCache }}
         proxy_ignore_headers Cache-Control Expires Set-Cookie Vary X-Accel-Expires;
                 {{- end }}
@@ -758,6 +785,33 @@ server {
                 {{- end }}
                 {{- if $l.Cache.AllowedMethods }}
         proxy_cache_methods{{ range $l.Cache.AllowedMethods }} {{ . }}{{ end }};
+                {{- end }}
+                {{- if $l.Cache.CacheUseStale }}
+        proxy_cache_use_stale{{ range $l.Cache.CacheUseStale }} {{ . }}{{ end }};
+                {{- end }}
+                {{- if $l.Cache.CacheRevalidate }}
+        proxy_cache_revalidate on;
+                {{- end }}
+                {{- if $l.Cache.CacheBackgroundUpdate }}
+        proxy_cache_background_update on;
+                {{- end }}
+                {{- if $l.Cache.CacheMinUses }}
+        proxy_cache_min_uses {{ $l.Cache.CacheMinUses }};
+                {{- end }}
+                {{- if $l.Cache.CacheLock }}
+        proxy_cache_lock on;
+                {{- end }}
+                {{- if $l.Cache.CacheLockTimeout }}
+        proxy_cache_lock_timeout {{ $l.Cache.CacheLockTimeout }};
+                {{- end }}
+                {{- if $l.Cache.CacheLockAge }}
+        proxy_cache_lock_age {{ $l.Cache.CacheLockAge }};
+                {{- end }}
+                {{- if $l.Cache.NoCacheConditions }}
+        proxy_no_cache{{ range $l.Cache.NoCacheConditions }} {{ . }}{{ end }};
+                {{- end }}
+                {{- if $l.Cache.CacheBypassConditions }}
+        proxy_cache_bypass{{ range $l.Cache.CacheBypassConditions }} {{ . }}{{ end }};
                 {{- end }}
                 {{- if gt (len $l.Cache.CachePurgeAllow) 0 }}
         proxy_cache_purge $cache_purge_{{ replaceAll $l.Cache.ZoneName "-" "_" }};
diff --git a/internal/configs/version2/nginx.virtualserver.tmpl b/internal/configs/version2/nginx.virtualserver.tmpl
index f4468e946..42dcb691a 100644
--- a/internal/configs/version2/nginx.virtualserver.tmpl
+++ b/internal/configs/version2/nginx.virtualserver.tmpl
@@ -41,7 +41,7 @@ limit_req_zone {{ $z.Key }} zone={{ $z.ZoneName }}:{{ $z.ZoneSize }} rate={{ $z.
 {{- end }}
 
 {{- range $c := .CacheZones }}
-proxy_cache_path {{ $c.Path }}{{ if $c.Levels }} levels={{ $c.Levels }}{{ end }} keys_zone={{ $c.Name }}:{{ $c.Size }};
+proxy_cache_path {{ $c.Path }}{{ if $c.Levels }} levels={{ $c.Levels }}{{ end }} keys_zone={{ $c.Name }}:{{ $c.Size }}{{ if $c.Inactive }} inactive={{ $c.Inactive }}{{ end }}{{ if $c.MaxSize }} max_size={{ $c.MaxSize }}{{ end }}{{ if $c.MinFree }} min_free={{ $c.MinFree }}{{ end }}{{ if $c.ManagerFiles }} manager_files={{ $c.ManagerFiles }}{{ end }}{{ if $c.ManagerSleep }} manager_sleep={{ $c.ManagerSleep }}{{ end }}{{ if $c.ManagerThreshold }} manager_threshold={{ $c.ManagerThreshold }}{{ end }}{{ if not $c.UseTempPath }} use_temp_path=off{{ end }};
 {{- end }}
 
 {{- $s := .Server }}
@@ -121,7 +121,7 @@ server {
     {{- with $s.Cache }}
     # Server-level cache configuration
     proxy_cache {{ $s.Cache.ZoneName }};
-    proxy_cache_key $scheme$proxy_host$request_uri;
+    proxy_cache_key {{ $s.Cache.CacheKey }};
             {{- if $s.Cache.OverrideUpstreamCache }}
     proxy_ignore_headers Cache-Control Expires Set-Cookie Vary X-Accel-Expires;
             {{- end }}
@@ -134,6 +134,33 @@ server {
             {{- if $s.Cache.AllowedMethods }}
     proxy_cache_methods{{ range $s.Cache.AllowedMethods }} {{ . }}{{ end }};
             {{- end }}
+            {{- if $s.Cache.CacheUseStale }}
+    proxy_cache_use_stale{{ range $s.Cache.CacheUseStale }} {{ . }}{{ end }};
+            {{- end }}
+            {{- if $s.Cache.CacheRevalidate }}
+    proxy_cache_revalidate on;
+            {{- end }}
+            {{- if $s.Cache.CacheBackgroundUpdate }}
+    proxy_cache_background_update on;
+            {{- end }}
+            {{- if $s.Cache.CacheMinUses }}
+    proxy_cache_min_uses {{ $s.Cache.CacheMinUses }};
+            {{- end }}
+            {{- if $s.Cache.CacheLock }}
+    proxy_cache_lock on;
+            {{- end }}
+            {{- if $s.Cache.CacheLockTimeout }}
+    proxy_cache_lock_timeout {{ $s.Cache.CacheLockTimeout }};
+            {{- end }}
+            {{- if $s.Cache.CacheLockAge }}
+    proxy_cache_lock_age {{ $s.Cache.CacheLockAge }};
+            {{- end }}
+            {{- if $s.Cache.NoCacheConditions }}
+    proxy_no_cache{{ range $s.Cache.NoCacheConditions }} {{ . }}{{ end }};
+            {{- end }}
+            {{- if $s.Cache.CacheBypassConditions }}
+    proxy_cache_bypass{{ range $s.Cache.CacheBypassConditions }} {{ . }}{{ end }};
+            {{- end }}
     {{- end }}
 
     {{- range $allow := $s.Allow }}
@@ -449,7 +476,7 @@ server {
 
         {{- with $l.Cache }}
         proxy_cache {{ $l.Cache.ZoneName }};
-        proxy_cache_key $scheme$proxy_host$request_uri;
+        proxy_cache_key {{ $l.Cache.CacheKey }};
                 {{- if $l.Cache.OverrideUpstreamCache }}
         proxy_ignore_headers Cache-Control Expires Set-Cookie Vary X-Accel-Expires;
                 {{- end }}
@@ -462,6 +489,33 @@ server {
                 {{- if $l.Cache.AllowedMethods }}
         proxy_cache_methods{{ range $l.Cache.AllowedMethods }} {{ . }}{{ end }};
                 {{- end }}
+                {{- if $l.Cache.CacheUseStale }}
+        proxy_cache_use_stale{{ range $l.Cache.CacheUseStale }} {{ . }}{{ end }};
+                {{- end }}
+                {{- if $l.Cache.CacheRevalidate }}
+        proxy_cache_revalidate on;
+                {{- end }}
+                {{- if $l.Cache.CacheBackgroundUpdate }}
+        proxy_cache_background_update on;
+                {{- end }}
+                {{- if $l.Cache.CacheMinUses }}
+        proxy_cache_min_uses {{ $l.Cache.CacheMinUses }};
+                {{- end }}
+                {{- if $l.Cache.CacheLock }}
+        proxy_cache_lock on;
+                {{- end }}
+                {{- if $l.Cache.CacheLockTimeout }}
+        proxy_cache_lock_timeout {{ $l.Cache.CacheLockTimeout }};
+                {{- end }}
+                {{- if $l.Cache.CacheLockAge }}
+        proxy_cache_lock_age {{ $l.Cache.CacheLockAge }};
+                {{- end }}
+                {{- if $l.Cache.NoCacheConditions }}
+        proxy_no_cache{{ range $l.Cache.NoCacheConditions }} {{ . }}{{ end }};
+                {{- end }}
+                {{- if $l.Cache.CacheBypassConditions }}
+        proxy_cache_bypass{{ range $l.Cache.CacheBypassConditions }} {{ . }}{{ end }};
+                {{- end }}
         {{- end }}
 
             {{-  if $l.GRPCPass }}
diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go
index 3a50fe2b0..be16b1f3f 100644
--- a/internal/configs/virtualserver.go
+++ b/internal/configs/virtualserver.go
@@ -2036,6 +2036,12 @@ func generateCacheConfig(cache *conf_v1.Cache, vsNamespace, vsName, ownerNamespa
 		uniqueZoneName = fmt.Sprintf("%s_%s_%s_%s_%s", vsNamespace, vsName, ownerNamespace, ownerName, cache.CacheZoneName)
 	}
 
+	// Set cache key with default if not provided
+	cacheKey := "$scheme$proxy_host$request_uri"
+	if cache.CacheKey != "" {
+		cacheKey = cache.CacheKey
+	}
+
 	cacheConfig := &version2.Cache{
 		ZoneName:              uniqueZoneName,
 		Time:                  cache.Time,
@@ -2045,6 +2051,35 @@ func generateCacheConfig(cache *conf_v1.Cache, vsNamespace, vsName, ownerNamespa
 		ZoneSize:              cache.CacheZoneSize,
 		OverrideUpstreamCache: cache.OverrideUpstreamCache,
 		Levels:                cache.Levels, // Pass Levels from Cache to CacheZone
+		Inactive:              cache.Inactive,
+		UseTempPath:           cache.UseTempPath,
+		MaxSize:               cache.MaxSize,
+		MinFree:               cache.MinFree,
+		CacheKey:              cacheKey,
+		CacheUseStale:         cache.CacheUseStale,
+		CacheRevalidate:       cache.CacheRevalidate,
+		CacheBackgroundUpdate: cache.CacheBackgroundUpdate,
+		CacheMinUses:          cache.CacheMinUses,
+	}
+
+	// Map lock fields
+	if cache.Lock != nil {
+		cacheConfig.CacheLock = cache.Lock.Enable
+		cacheConfig.CacheLockTimeout = cache.Lock.Timeout
+		cacheConfig.CacheLockAge = cache.Lock.Age
+	}
+
+	// Map manager fields
+	if cache.Manager != nil {
+		cacheConfig.ManagerFiles = cache.Manager.Files
+		cacheConfig.ManagerSleep = cache.Manager.Sleep
+		cacheConfig.ManagerThreshold = cache.Manager.Threshold
+	}
+
+	// Map conditions
+	if cache.Conditions != nil {
+		cacheConfig.NoCacheConditions = cache.Conditions.NoCache
+		cacheConfig.CacheBypassConditions = cache.Conditions.Bypass
 	}
 
 	// Convert allowed codes to proxy_cache_valid entries
@@ -2074,10 +2109,17 @@ func addCacheZone(cacheZones *[]version2.CacheZone, cache *version2.Cache) {
 	}
 
 	cacheZone := version2.CacheZone{
-		Name:   cache.ZoneName,
-		Size:   zoneSize,
-		Path:   fmt.Sprintf("/var/cache/nginx/%s", cache.ZoneName),
-		Levels: cache.Levels, // Pass Levels from Cache to CacheZone
+		Name:             cache.ZoneName,
+		Size:             zoneSize,
+		Path:             fmt.Sprintf("/var/cache/nginx/%s", cache.ZoneName),
+		Levels:           cache.Levels, // Pass Levels from Cache to CacheZone
+		Inactive:         cache.Inactive,
+		UseTempPath:      cache.UseTempPath,
+		MaxSize:          cache.MaxSize,
+		MinFree:          cache.MinFree,
+		ManagerFiles:     cache.ManagerFiles,
+		ManagerSleep:     cache.ManagerSleep,
+		ManagerThreshold: cache.ManagerThreshold,
 	}
 
 	// Check for duplicates
diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go
index cbec763df..90376fe69 100644
--- a/pkg/apis/configuration/v1/types.go
+++ b/pkg/apis/configuration/v1/types.go
@@ -1036,6 +1036,60 @@ type SuppliedIn struct {
 	Query []string `json:"query"`
 }
 
+// CacheManager defines cache manager process parameters for controlling the cache manager process behavior.
+// The cache manager monitors the maximum cache size and removes the least recently used data when the size is exceeded.
+type CacheManager struct {
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:Minimum=1
+	// Files sets the maximum number of files that will be deleted in one iteration by the cache manager.
+	// During one iteration no more than manager_files items are deleted (by default, 100).
+	Files *int `json:"files,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:Pattern=`^[0-9]+[mu]?s$`
+	// Sleep sets the pause between cache manager iterations.
+	// Between iterations, a pause configured by manager_sleep (by default, 50 milliseconds) is made.
+	Sleep string `json:"sleep,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:Pattern=`^[0-9]+[mu]?s$`
+	// Threshold sets the maximum duration of one cache manager iteration.
+	// The duration of one iteration is limited by manager_threshold (by default, 200 milliseconds).
+	Threshold string `json:"threshold,omitempty"`
+}
+
+// CacheLock defines cache locking parameters. When enabled, only one request at a time will be allowed to populate a new cache element.
+// Other requests of the same cache element will either wait for a response to appear in the cache or the cache lock for this element to be released.
+// +kubebuilder:validation:XValidation:rule="(!has(self.timeout) && !has(self.age)) || self.enable",message="timeout or age require enable=true"
+type CacheLock struct {
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:default=false
+	// Enable sets whether cache locking is enabled (proxy_cache_lock).
+	// When enabled, only one request at a time will be allowed to populate a new cache element according to the proxy_cache_key.
+	Enable bool `json:"enable,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:Pattern=`^[0-9]+[smhd]$`
+	// Timeout sets a timeout for proxy_cache_lock.
+	// When the time expires, the request will be passed to the proxied server, however, the response will not be cached.
+	Timeout string `json:"timeout,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:Pattern=`^[0-9]+[smhd]$`
+	// Age sets the maximum time a cache lock can be held (proxy_cache_lock_age).
+	// If the last request passed to the proxied server for populating a new cache element has not completed for the specified time, one more request may be passed.
+	Age string `json:"age,omitempty"`
+}
+
+// CacheConditions defines conditions for cache bypass and no-cache behavior.
+// These use NGINX variables to make dynamic caching decisions based on request characteristics.
+type CacheConditions struct {
+	// +kubebuilder:validation:Optional
+	// NoCache defines conditions under which the response will not be saved to a cache (proxy_no_cache).
+	// If at least one value of the string parameters is not empty and is not equal to "0" then the response will not be saved.
+	NoCache []string `json:"noCache,omitempty"`
+	// +kubebuilder:validation:Optional
+	// Bypass defines conditions under which the response will not be taken from a cache (proxy_cache_bypass).
+	// If at least one value of the string parameters is not empty and is not equal to "0" then the response will not be taken from the cache.
+	Bypass []string `json:"bypass,omitempty"`
+}
+
 // Cache defines a cache policy for proxy caching.
 // +kubebuilder:validation:XValidation:rule="!has(self.allowedCodes) || (has(self.allowedCodes) && has(self.time))",message="time is required when allowedCodes is specified"
 type Cache struct {
@@ -1046,9 +1100,9 @@ type Cache struct {
 	// Single lowercase letters are also allowed. Examples: "cache", "my_cache", "cache1".
 	CacheZoneName string `json:"cacheZoneName"`
 	// +kubebuilder:validation:Required
-	// +kubebuilder:validation:Pattern=`^[0-9]+[kmg]$`
+	// +kubebuilder:validation:Pattern=`^[0-9]+[kmgKMG]$`
 	// CacheZoneSize defines the size of the cache zone. Must be a number followed by a size unit:
-	// 'k' for kilobytes, 'm' for megabytes, or 'g' for gigabytes.
+	// 'k' or 'K' for kilobytes, 'm' or 'M' for megabytes, or 'g' or 'G' for gigabytes.
 	// Examples: "10m", "1g", "512k".
 	CacheZoneSize string `json:"cacheZoneSize"`
 	// +kubebuilder:validation:Optional
@@ -1096,4 +1150,60 @@ type Cache struct {
 	// Examples: "1:2", "2:2", "1:2:2".
 	// Invalid: "3:1", "1:3", "1:2:3".
 	Levels string `json:"levels,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:Pattern=`^[0-9]+[smhd]$`
+	// Inactive sets the time after which cached data that are not accessed get removed from the cache (inactive parameter).
+	// By default, inactive is set to 10 minutes.
+	Inactive string `json:"inactive,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:default=false
+	// UseTempPath controls whether temporary files and the cache are put on different file systems (use_temp_path parameter).
+	// If set to false, temporary files will be put directly in the cache directory (use_temp_path=off).
+	// Default: false (use_temp_path=off, which puts temp files directly in cache directory for better performance).
+	UseTempPath bool `json:"useTempPath,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:Pattern=`^[0-9]+[kmgKMG]$`
+	// MaxSize sets the maximum cache size (max_size parameter).
+	// When the size is exceeded, the cache manager removes the least recently used data.
+	MaxSize string `json:"maxSize,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:Pattern=`^[0-9]+[kmgKMG]$`
+	// MinFree sets the minimum amount of free space required on the file system with cache (min_free parameter).
+	// When there is not enough free space, the cache manager removes the least recently used data.
+	MinFree string `json:"minFree,omitempty"`
+	// +kubebuilder:validation:Optional
+	// Manager configures the cache manager process parameters (manager_files, manager_sleep, manager_threshold).
+	Manager *CacheManager `json:"manager,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:MaxLength=1024
+	// +kubebuilder:validation:XValidation:rule="!self.contains('$(') && !self.contains('`') && !self.contains(';') && !self.contains('&&') && !self.contains('||')",message="cache key must not contain command execution patterns: $(, `, ;, &&, ||"
+	// CacheKey defines a key for caching (proxy_cache_key).
+	// By default, close to "$scheme$proxy_host$uri$is_args$args".
+	// Must not contain command execution patterns: $(, `, ;, &&, ||
+	CacheKey string `json:"cacheKey,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:MaxItems=11
+	// CacheUseStale determines in which cases a stale cached response can be used (proxy_cache_use_stale).
+	// Valid parameters: error, timeout, invalid_header, updating, http_500, http_502, http_503, http_504, http_403, http_404, http_429, off.
+	CacheUseStale []string `json:"cacheUseStale,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:default=false
+	// CacheRevalidate enables revalidation of expired cache items using conditional requests (proxy_cache_revalidate).
+	// Uses "If-Modified-Since" and "If-None-Match" header fields.
+	CacheRevalidate bool `json:"cacheRevalidate,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:default=false
+	// CacheBackgroundUpdate allows starting a background subrequest to update an expired cache item (proxy_cache_background_update).
+	// A stale cached response is returned to the client while the cache is being updated.
+	CacheBackgroundUpdate bool `json:"cacheBackgroundUpdate,omitempty"`
+	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:Minimum=1
+	// CacheMinUses sets the number of requests after which the response will be cached (proxy_cache_min_uses).
+	CacheMinUses *int `json:"cacheMinUses,omitempty"`
+	// +kubebuilder:validation:Optional
+	// Lock configures cache locking to prevent multiple identical requests from populating the same cache element simultaneously.
+	Lock *CacheLock `json:"lock,omitempty"`
+	// +kubebuilder:validation:Optional
+	// Conditions defines when responses should not be cached or taken from cache.
+	Conditions *CacheConditions `json:"conditions,omitempty"`
 }
diff --git a/pkg/apis/configuration/v1/zz_generated.deepcopy.go b/pkg/apis/configuration/v1/zz_generated.deepcopy.go
index 846948468..e75bfc28e 100644
--- a/pkg/apis/configuration/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/configuration/v1/zz_generated.deepcopy.go
@@ -202,6 +202,31 @@ func (in *Cache) DeepCopyInto(out *Cache) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.Manager != nil {
+		in, out := &in.Manager, &out.Manager
+		*out = new(CacheManager)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.CacheUseStale != nil {
+		in, out := &in.CacheUseStale, &out.CacheUseStale
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.CacheMinUses != nil {
+		in, out := &in.CacheMinUses, &out.CacheMinUses
+		*out = new(int)
+		**out = **in
+	}
+	if in.Lock != nil {
+		in, out := &in.Lock, &out.Lock
+		*out = new(CacheLock)
+		**out = **in
+	}
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = new(CacheConditions)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 
@@ -215,6 +240,69 @@ func (in *Cache) DeepCopy() *Cache {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CacheConditions) DeepCopyInto(out *CacheConditions) {
+	*out = *in
+	if in.NoCache != nil {
+		in, out := &in.NoCache, &out.NoCache
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.Bypass != nil {
+		in, out := &in.Bypass, &out.Bypass
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CacheConditions.
+func (in *CacheConditions) DeepCopy() *CacheConditions {
+	if in == nil {
+		return nil
+	}
+	out := new(CacheConditions)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CacheLock) DeepCopyInto(out *CacheLock) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CacheLock.
+func (in *CacheLock) DeepCopy() *CacheLock {
+	if in == nil {
+		return nil
+	}
+	out := new(CacheLock)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CacheManager) DeepCopyInto(out *CacheManager) {
+	*out = *in
+	if in.Files != nil {
+		in, out := &in.Files, &out.Files
+		*out = new(int)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CacheManager.
+func (in *CacheManager) DeepCopy() *CacheManager {
+	if in == nil {
+		return nil
+	}
+	out := new(CacheManager)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *CertManager) DeepCopyInto(out *CertManager) {
 	*out = *in
diff --git a/pkg/apis/configuration/validation/policy.go b/pkg/apis/configuration/validation/policy.go
index af0e2bc29..3e04f6037 100644
--- a/pkg/apis/configuration/validation/policy.go
+++ b/pkg/apis/configuration/validation/policy.go
@@ -12,6 +12,7 @@ import (
 	validation2 "github.com/nginx/kubernetes-ingress/internal/validation"
 	v1 "github.com/nginx/kubernetes-ingress/pkg/apis/configuration/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
+	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/validation"
 	"k8s.io/apimachinery/pkg/util/validation/field"
 )
@@ -438,8 +439,86 @@ func validateCache(cache *v1.Cache, fieldPath *field.Path, isPlus bool) field.Er
 
 	allErrs = append(allErrs, validateCacheAllowedCodes(cache, fieldPath)...)
 
+	// Validate NGINX Plus features
 	allErrs = append(allErrs, validateCachePlusFeatures(cache, fieldPath, isPlus)...)
 
+	// Validate time fields
+	if cache.Inactive != "" {
+		allErrs = append(allErrs, validateTime(cache.Inactive, fieldPath.Child("inactive"))...)
+	}
+
+	// Validate size fields
+	if cache.MaxSize != "" {
+		allErrs = append(allErrs, validateOffset(cache.MaxSize, fieldPath.Child("maxSize"))...)
+	}
+	if cache.MinFree != "" {
+		allErrs = append(allErrs, validateOffset(cache.MinFree, fieldPath.Child("minFree"))...)
+	}
+
+	// Validate manager fields
+	if cache.Manager != nil {
+		managerPath := fieldPath.Child("manager")
+		if cache.Manager.Files != nil && *cache.Manager.Files <= 0 {
+			allErrs = append(allErrs, field.Invalid(managerPath.Child("files"), *cache.Manager.Files, "must be a positive integer"))
+		}
+		if cache.Manager.Sleep != "" {
+			allErrs = append(allErrs, validateTime(cache.Manager.Sleep, managerPath.Child("sleep"))...)
+		}
+		if cache.Manager.Threshold != "" {
+			allErrs = append(allErrs, validateTime(cache.Manager.Threshold, managerPath.Child("threshold"))...)
+		}
+	}
+
+	// Validate cache min uses
+	if cache.CacheMinUses != nil && *cache.CacheMinUses <= 0 {
+		allErrs = append(allErrs, field.Invalid(fieldPath.Child("cacheMinUses"), *cache.CacheMinUses, "must be a positive integer"))
+	}
+
+	// Validate lock fields
+	if cache.Lock != nil {
+		lockPath := fieldPath.Child("lock")
+		if cache.Lock.Timeout != "" {
+			allErrs = append(allErrs, validateTime(cache.Lock.Timeout, lockPath.Child("timeout"))...)
+		}
+		if cache.Lock.Age != "" {
+			allErrs = append(allErrs, validateTime(cache.Lock.Age, lockPath.Child("age"))...)
+		}
+	}
+
+	// Validate cache key
+	if cache.CacheKey != "" {
+		if err := ValidateEscapedString(cache.CacheKey); err != nil {
+			allErrs = append(allErrs, field.Invalid(fieldPath.Child("cacheKey"), cache.CacheKey, err.Error()))
+		}
+		// Cache keys support both ${var} and $var NGINX syntax, so we skip variable braces validation
+		// but still validate basic syntax rules like not ending with $
+		if strings.HasSuffix(cache.CacheKey, "$") {
+			allErrs = append(allErrs, field.Invalid(fieldPath.Child("cacheKey"), cache.CacheKey, "must not end with $"))
+		}
+	}
+
+	// Validate conditions
+	if cache.Conditions != nil {
+		conditionsPath := fieldPath.Child("conditions")
+		for i, condition := range cache.Conditions.NoCache {
+			if condition != "" {
+				if err := ValidateEscapedString(condition); err != nil {
+					allErrs = append(allErrs, field.Invalid(conditionsPath.Child("noCache").Index(i), condition, err.Error()))
+				}
+			}
+		}
+		for i, condition := range cache.Conditions.Bypass {
+			if condition != "" {
+				if err := ValidateEscapedString(condition); err != nil {
+					allErrs = append(allErrs, field.Invalid(conditionsPath.Child("bypass").Index(i), condition, err.Error()))
+				}
+			}
+		}
+	}
+
+	// Validate use stale
+	allErrs = append(allErrs, validateCacheUseStale(cache, fieldPath)...)
+
 	return allErrs
 }
 
@@ -512,6 +591,30 @@ func validateCachePlusFeatures(cache *v1.Cache, fieldPath *field.Path, isPlus bo
 	return allErrs
 }
 
+// validateCacheUseStale validates the cacheUseStale field values
+// The directive's parameters match the parameters of the proxy_next_upstream directive plus "updating"
+func validateCacheUseStale(cache *v1.Cache, fieldPath *field.Path) field.ErrorList {
+	if len(cache.CacheUseStale) == 0 {
+		return nil
+	}
+
+	allErrs := field.ErrorList{}
+	allParams := sets.Set[string]{}
+
+	for _, para := range cache.CacheUseStale {
+		// Check if parameter is valid (either from validNextUpstreamParams or "updating" which is specific to cache)
+		if !validNextUpstreamParams[para] && para != "updating" {
+			allErrs = append(allErrs, field.Invalid(fieldPath.Child("cacheUseStale"), para, "not a valid parameter"))
+		}
+		if allParams.Has(para) {
+			allErrs = append(allErrs, field.Invalid(fieldPath.Child("cacheUseStale"), para, "can not have duplicate parameters"))
+		} else {
+			allParams.Insert(para)
+		}
+	}
+	return allErrs
+}
+
 func validateLogConf(logConf *v1.SecurityLog, fieldPath *field.Path, bundleMode bool) field.ErrorList {
 	allErrs := field.ErrorList{}
 



Test patch:
diff --git a/internal/configs/version2/templates_test.go b/internal/configs/version2/templates_test.go
index cc928a154..d4f4ca7c1 100644
--- a/internal/configs/version2/templates_test.go
+++ b/internal/configs/version2/templates_test.go
@@ -950,7 +950,7 @@ func TestExecuteVirtualServerTemplateWithCachePolicyNGINXPlus(t *testing.T) {
 	}
 
 	// Check cache zone declaration
-	expectedCacheZone := "proxy_cache_path /var/cache/nginx/test_cache_full_advanced levels=2:2 keys_zone=test_cache_full_advanced:50m;"
+	expectedCacheZone := "proxy_cache_path /var/cache/nginx/test_cache_full_advanced levels=2:2 keys_zone=test_cache_full_advanced:50m use_temp_path=off;"
 	if !bytes.Contains(got, []byte(expectedCacheZone)) {
 		t.Errorf("Expected cache zone declaration: %s", expectedCacheZone)
 	}
@@ -1001,6 +1001,65 @@ func TestExecuteVirtualServerTemplateWithCachePolicyNGINXPlus(t *testing.T) {
 	t.Log(string(got))
 }
 
+func TestExecuteVirtualServerTemplateWithExtendedCachePolicy(t *testing.T) {
+	t.Parallel()
+	executor := newTmplExecutorNGINXPlus(t)
+	got, err := executor.ExecuteVirtualServerTemplate(&virtualServerCfgWithExtendedCachePolicy)
+	if err != nil {
+		t.Error(err)
+	}
+
+	// Check extended cache zone declaration with new fields
+	expectedCacheZone := "proxy_cache_path /var/cache/nginx/extended_cache_zone levels=2:2 keys_zone=extended_cache_zone:100m inactive=7d max_size=2g manager_files=500 manager_sleep=200ms manager_threshold=1s use_temp_path=off;"
+	if !bytes.Contains(got, []byte(expectedCacheZone)) {
+		t.Errorf("Expected extended cache zone declaration: %s", expectedCacheZone)
+	}
+
+	// Check cache lock configuration
+	expectedCacheLock := "proxy_cache_lock on;"
+	if !bytes.Contains(got, []byte(expectedCacheLock)) {
+		t.Errorf("Expected cache lock directive: %s", expectedCacheLock)
+	}
+
+	expectedCacheLockTimeout := "proxy_cache_lock_timeout 60s;"
+	if !bytes.Contains(got, []byte(expectedCacheLockTimeout)) {
+		t.Errorf("Expected cache lock timeout directive: %s", expectedCacheLockTimeout)
+	}
+
+	// Check cache conditions
+	expectedNoCache := "proxy_no_cache $cookie_admin;"
+	if !bytes.Contains(got, []byte(expectedNoCache)) {
+		t.Errorf("Expected no-cache condition directive: %s", expectedNoCache)
+	}
+
+	expectedCacheBypass := "proxy_cache_bypass $http_cache_control;"
+	if !bytes.Contains(got, []byte(expectedCacheBypass)) {
+		t.Errorf("Expected cache bypass condition directive: %s", expectedCacheBypass)
+	}
+
+	// Check extended cache directives
+	expectedCacheDirectives := []string{
+		"proxy_cache extended_cache_zone;",
+		"proxy_cache_key $scheme$host$request_uri$args;",
+		"proxy_cache_min_uses 3;",
+		"proxy_cache_valid 200 1h;",
+		"proxy_cache_valid 404 10m;",
+		"proxy_cache_valid any 5m;",
+		"proxy_cache_background_update on;",
+		"proxy_cache_use_stale error timeout updating;",
+		"proxy_cache_revalidate on;",
+	}
+
+	for _, directive := range expectedCacheDirectives {
+		if !bytes.Contains(got, []byte(directive)) {
+			t.Errorf("Expected cache directive: %s", directive)
+		}
+	}
+
+	snaps.MatchSnapshot(t, string(got))
+	t.Log(string(got))
+}
+
 func TestExecuteVirtualServerTemplateWithCachePolicyOSS(t *testing.T) {
 	t.Parallel()
 	executor := newTmplExecutorNGINX(t)
@@ -1010,7 +1069,7 @@ func TestExecuteVirtualServerTemplateWithCachePolicyOSS(t *testing.T) {
 	}
 
 	// Check cache zone declaration
-	expectedCacheZone := "proxy_cache_path /var/cache/nginx/test_cache_basic_cache levels=1:2 keys_zone=test_cache_basic_cache:10m;"
+	expectedCacheZone := "proxy_cache_path /var/cache/nginx/test_cache_basic_cache levels=1:2 keys_zone=test_cache_basic_cache:10m use_temp_path=off;"
 	if !bytes.Contains(got, []byte(expectedCacheZone)) {
 		t.Errorf("Expected cache zone declaration: %s", expectedCacheZone)
 	}
@@ -2957,6 +3016,7 @@ var (
 				CachePurgeAllow:       []string{"127.0.0.1", "10.0.0.0/8", "192.168.1.0/24"},
 				OverrideUpstreamCache: true,
 				Levels:                "2:2",
+				CacheKey:              "$scheme$proxy_host$request_uri",
 			},
 			Locations: []Location{
 				{
@@ -2972,6 +3032,7 @@ var (
 						CachePurgeAllow:       nil,
 						OverrideUpstreamCache: false,
 						Levels:                "",
+						CacheKey:              "$scheme$proxy_host$request_uri",
 					},
 				},
 			},
@@ -3017,6 +3078,7 @@ var (
 				CachePurgeAllow:       []string{"127.0.0.1"}, // This should be ignored for OSS
 				OverrideUpstreamCache: true,
 				Levels:                "1:2",
+				CacheKey:              "$scheme$proxy_host$request_uri",
 			},
 			Locations: []Location{
 				{
@@ -3032,12 +3094,76 @@ var (
 						CachePurgeAllow:       nil,
 						OverrideUpstreamCache: false,
 						Levels:                "",
+						CacheKey:              "$scheme$proxy_host$request_uri",
 					},
 				},
 			},
 		},
 	}
 
+	virtualServerCfgWithExtendedCachePolicy = VirtualServerConfig{
+		CacheZones: []CacheZone{
+			{
+				Name:             "extended_cache_zone",
+				Size:             "100m",
+				Path:             "/var/cache/nginx/extended_cache_zone",
+				Levels:           "2:2",
+				UseTempPath:      false,
+				MaxSize:          "2g",
+				Inactive:         "7d",
+				ManagerFiles:     createPointerFromInt(500),
+				ManagerSleep:     "200ms",
+				ManagerThreshold: "1s",
+			},
+		},
+		Upstreams: []Upstream{
+			{
+				Name: "extended-upstream",
+				Servers: []UpstreamServer{
+					{
+						Address: "10.0.0.50:8001",
+					},
+				},
+			},
+		},
+		Server: Server{
+			ServerName:   "extended.example.com",
+			StatusZone:   "extended.example.com",
+			ServerTokens: "off",
+			// Server-level cache policy with all extended options
+			Cache: &Cache{
+				ZoneName:              "extended_cache_zone",
+				ZoneSize:              "100m",
+				Levels:                "2:2",
+				Inactive:              "7d",
+				UseTempPath:           false,
+				MaxSize:               "2g",
+				ManagerFiles:          createPointerFromInt(500),
+				ManagerSleep:          "200ms",
+				ManagerThreshold:      "1s",
+				CacheKey:              "$scheme$host$request_uri$args",
+				OverrideUpstreamCache: false,
+				Valid:                 map[string]string{"200": "1h", "404": "10m", "any": "5m"},
+				AllowedMethods:        nil,
+				CacheUseStale:         []string{"error", "timeout", "updating"},
+				CacheRevalidate:       true,
+				CacheBackgroundUpdate: true,
+				CacheMinUses:          createPointerFromInt(3),
+				CachePurgeAllow:       nil,
+				CacheLock:             true,
+				CacheLockTimeout:      "60s",
+				NoCacheConditions:     []string{"$cookie_admin"},
+				CacheBypassConditions: []string{"$http_cache_control"},
+			},
+			Locations: []Location{
+				{
+					Path:      "/api",
+					ProxyPass: "http://extended-upstream",
+				},
+			},
+		},
+	}
+
 	transportServerCfg = TransportServerConfig{
 		Upstreams: []StreamUpstream{
 			{
diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go
index 9ed0ccda0..fd08b3dd9 100644
--- a/internal/configs/virtualserver_test.go
+++ b/internal/configs/virtualserver_test.go
@@ -11691,6 +11691,7 @@ func TestGenerateVirtualServerConfigCache(t *testing.T) {
 						CachePurgeAllow:       nil,
 						OverrideUpstreamCache: false,
 						Levels:                "",
+						CacheKey:              "$scheme$proxy_host$request_uri",
 					},
 					Locations: []version2.Location{
 						{
@@ -11857,6 +11858,7 @@ func TestGenerateVirtualServerConfigCache(t *testing.T) {
 								CachePurgeAllow:       nil,
 								OverrideUpstreamCache: false,
 								Levels:                "",
+								CacheKey:              "$scheme$proxy_host$request_uri",
 							},
 						},
 						{
@@ -11954,6 +11956,15 @@ func TestGenerateVirtualServerConfigCache(t *testing.T) {
 								Time:                  "2h",
 								OverrideUpstreamCache: true,
 								CachePurgeAllow:       []string{"127.0.0.1"},
+								CacheKey:              "$scheme$proxy_host$request_uri$is_args$args",
+								CacheBackgroundUpdate: true,
+								CacheUseStale:         []string{"error", "timeout", "http_503"},
+								Levels:                "2:2",
+								MinFree:               "100m",
+								Conditions: &conf_v1.CacheConditions{
+									NoCache: []string{"$http_pragma", "$http_authorization"},
+									Bypass:  []string{"$cookie_nocache", "$arg_nocache"},
+								},
 							},
 						},
 					},
@@ -12019,10 +12030,11 @@ func TestGenerateVirtualServerConfigCache(t *testing.T) {
 				LimitReqZones: []version2.LimitReqZone{},
 				CacheZones: []version2.CacheZone{
 					{
-						Name:   "default_cafe_default_tea-vsr_vsr-cache",
-						Size:   "20m",
-						Path:   "/var/cache/nginx/default_cafe_default_tea-vsr_vsr-cache",
-						Levels: "",
+						Name:    "default_cafe_default_tea-vsr_vsr-cache",
+						Size:    "20m",
+						Path:    "/var/cache/nginx/default_cafe_default_tea-vsr_vsr-cache",
+						Levels:  "2:2",
+						MinFree: "100m",
 					},
 				},
 				Server: version2.Server{
@@ -12053,7 +12065,13 @@ func TestGenerateVirtualServerConfigCache(t *testing.T) {
 								AllowedMethods:        nil,
 								CachePurgeAllow:       []string{"127.0.0.1"},
 								OverrideUpstreamCache: true,
-								Levels:                "",
+								Levels:                "2:2",
+								MinFree:               "100m",
+								CacheKey:              "$scheme$proxy_host$request_uri$is_args$args",
+								CacheBackgroundUpdate: true,
+								CacheUseStale:         []string{"error", "timeout", "http_503"},
+								NoCacheConditions:     []string{"$http_pragma", "$http_authorization"},
+								CacheBypassConditions: []string{"$cookie_nocache", "$arg_nocache"},
 							},
 						},
 						{
@@ -12074,6 +12092,167 @@ func TestGenerateVirtualServerConfigCache(t *testing.T) {
 				},
 			},
 		},
+		{
+			msg: "cache policy with extended fields",
+			virtualServerEx: VirtualServerEx{
+				VirtualServer: &conf_v1.VirtualServer{
+					ObjectMeta: meta_v1.ObjectMeta{
+						Name:      "extended-cache",
+						Namespace: "default",
+					},
+					Spec: conf_v1.VirtualServerSpec{
+						Host: "cache.example.com",
+						Policies: []conf_v1.PolicyReference{
+							{
+								Name: "extended-cache-policy",
+							},
+						},
+						Upstreams: []conf_v1.Upstream{
+							{
+								Name:    "backend",
+								Service: "backend-svc",
+								Port:    80,
+							},
+						},
+						Routes: []conf_v1.Route{
+							{
+								Path: "/api",
+								Action: &conf_v1.Action{
+									Pass: "backend",
+								},
+							},
+						},
+					},
+				},
+				Policies: map[string]*conf_v1.Policy{
+					"default/extended-cache-policy": {
+						ObjectMeta: meta_v1.ObjectMeta{
+							Name:      "extended-cache-policy",
+							Namespace: "default",
+						},
+						Spec: conf_v1.PolicySpec{
+							Cache: &conf_v1.Cache{
+								CacheZoneName: "extended-cache",
+								CacheZoneSize: "100m",
+								CacheKey:      "$scheme$host$request_uri$args",
+								CacheMinUses:  createPointerFromInt(3),
+								UseTempPath:   false,
+								MaxSize:       "2g",
+								Inactive:      "7d",
+								Manager: &conf_v1.CacheManager{
+									Files:     createPointerFromInt(500),
+									Sleep:     "200ms",
+									Threshold: "1s",
+								},
+								Lock: &conf_v1.CacheLock{
+									Enable:  true,
+									Timeout: "60s",
+								},
+								Conditions: &conf_v1.CacheConditions{
+									NoCache: []string{"$cookie_admin"},
+									Bypass:  []string{"$http_cache_control"},
+								},
+								AllowedCodes: []intstr.IntOrString{
+									intstr.FromString("200"),
+									intstr.FromString("404"),
+									intstr.FromString("any"),
+								},
+								Time:                  "1h",
+								CacheBackgroundUpdate: true,
+								CacheRevalidate:       true,
+							},
+						},
+					},
+				},
+				Endpoints: map[string][]string{
+					"default/backend-svc:80": {
+						"10.0.0.40:80",
+					},
+				},
+			},
+			expected: version2.VirtualServerConfig{
+				Upstreams: []version2.Upstream{
+					{
+						UpstreamLabels: version2.UpstreamLabels{
+							Service:           "backend-svc",
+							ResourceType:      "virtualserver",
+							ResourceName:      "extended-cache",
+							ResourceNamespace: "default",
+						},
+						Name: "vs_default_extended-cache_backend",
+						Servers: []version2.UpstreamServer{
+							{
+								Address: "10.0.0.40:80",
+							},
+						},
+					},
+				},
+				HTTPSnippets:  []string{},
+				LimitReqZones: []version2.LimitReqZone{},
+				CacheZones: []version2.CacheZone{
+					{
+						Name:             "default_extended-cache_extended-cache",
+						Size:             "100m",
+						Path:             "/var/cache/nginx/default_extended-cache_extended-cache",
+						Levels:           "",
+						Inactive:         "7d",
+						UseTempPath:      false,
+						MaxSize:          "2g",
+						MinFree:          "",
+						ManagerFiles:     createPointerFromInt(500),
+						ManagerSleep:     "200ms",
+						ManagerThreshold: "1s",
+					},
+				},
+				Server: version2.Server{
+					ServerName:   "cache.example.com",
+					StatusZone:   "cache.example.com",
+					ServerTokens: "off",
+					VSNamespace:  "default",
+					VSName:       "extended-cache",
+					Cache: &version2.Cache{
+						ZoneName:              "default_extended-cache_extended-cache",
+						ZoneSize:              "100m",
+						Levels:                "",
+						Inactive:              "7d",
+						UseTempPath:           false,
+						MaxSize:               "2g",
+						MinFree:               "",
+						ManagerFiles:          createPointerFromInt(500),
+						ManagerSleep:          "200ms",
+						ManagerThreshold:      "1s",
+						CacheKey:              "$scheme$host$request_uri$args",
+						OverrideUpstreamCache: false,
+						Time:                  "1h",
+						Valid:                 map[string]string{"200": "1h", "404": "1h", "any": "1h"},
+						AllowedMethods:        nil,
+						CacheUseStale:         nil,
+						CacheRevalidate:       true,
+						CacheBackgroundUpdate: true,
+						CacheMinUses:          createPointerFromInt(3),
+						CachePurgeAllow:       nil,
+						CacheLock:             true,
+						CacheLockTimeout:      "60s",
+						CacheLockAge:          "",
+						NoCacheConditions:     []string{"$cookie_admin"},
+						CacheBypassConditions: []string{"$http_cache_control"},
+					},
+					Locations: []version2.Location{
+						{
+							Path:                     "/api",
+							ProxyPass:                "http://vs_default_extended-cache_backend",
+							ProxyNextUpstream:        "error timeout",
+							ProxyNextUpstreamTimeout: "0s",
+							ProxyNextUpstreamTries:   0,
+							ProxySSLName:             "backend-svc.default.svc",
+							ProxyPassRequestHeaders:  true,
+							ProxySetHeaders:          []version2.Header{{Name: "Host", Value: "$host"}},
+							ServiceName:              "backend-svc",
+						},
+					},
+				},
+			},
+		},
 	}
 
 	baseCfgParams := ConfigParams{
@@ -13098,6 +13277,7 @@ func TestGeneratePolicies(t *testing.T) {
 					ZoneName: "default_test_basic-cache",
 					ZoneSize: "10m",
 					Valid:    map[string]string{},
+					CacheKey: "$scheme$proxy_host$request_uri",
 				},
 			},
 			msg: "basic cache policy reference",
@@ -13124,6 +13304,30 @@ func TestGeneratePolicies(t *testing.T) {
 							Time:                  "1h",
 							OverrideUpstreamCache: true,
 							Levels:                "1:2",
+							Inactive:              "2d",
+							UseTempPath:           false,
+							MaxSize:               "5g",
+							MinFree:               "500m",
+							Manager: &conf_v1.CacheManager{
+								Files:     &[]int{1000}[0],
+								Sleep:     "50ms",
+								Threshold: "150ms",
+							},
+							CacheKey:              "$scheme$proxy_host$request_uri$is_args$args",
+							CacheUseStale:         []string{"error", "timeout", "invalid_header", "updating", "http_500", "http_502", "http_503"},
+							CacheRevalidate:       true,
+							CacheBackgroundUpdate: true,
+							CacheMinUses:          &[]int{3}[0],
+							Lock: &conf_v1.CacheLock{
+								Enable:  true,
+								Timeout: "10s",
+								Age:     "30s",
+							},
+							CachePurgeAllow: []string{"127.0.0.1", "10.0.0.0/8"},
+							Conditions: &conf_v1.CacheConditions{
+								NoCache: []string{"$http_pragma", "$http_authorization"},
+								Bypass:  []string{"$cookie_nocache", "$arg_nocache", "$arg_comment"},
+							},
 						},
 					},
 				},
@@ -13138,6 +13342,24 @@ func TestGeneratePolicies(t *testing.T) {
 					AllowedMethods:        []string{"GET", "HEAD", "POST"},
 					OverrideUpstreamCache: true,
 					Levels:                "1:2",
+					Inactive:              "2d",
+					UseTempPath:           false,
+					MaxSize:               "5g",
+					MinFree:               "500m",
+					ManagerFiles:          &[]int{1000}[0],
+					ManagerSleep:          "50ms",
+					ManagerThreshold:      "150ms",
+					CacheKey:              "$scheme$proxy_host$request_uri$is_args$args",
+					CacheUseStale:         []string{"error", "timeout", "invalid_header", "updating", "http_500", "http_502", "http_503"},
+					CacheRevalidate:       true,
+					CacheBackgroundUpdate: true,
+					CacheMinUses:          &[]int{3}[0],
+					CacheLock:             true,
+					CacheLockTimeout:      "10s",
+					CacheLockAge:          "30s",
+					CachePurgeAllow:       []string{"127.0.0.1", "10.0.0.0/8"},
+					NoCacheConditions:     []string{"$http_pragma", "$http_authorization"},
+					CacheBypassConditions: []string{"$cookie_nocache", "$arg_nocache", "$arg_comment"},
 				},
 			},
 			msg: "full cache policy with all options",
@@ -13180,6 +13402,7 @@ func TestGeneratePolicies(t *testing.T) {
 						"301": "30m",
 						"404": "30m",
 					},
+					CacheKey: "$scheme$proxy_host$request_uri",
 				},
 			},
 			msg: "cache policy with specific status codes",
@@ -13215,6 +13438,7 @@ func TestGeneratePolicies(t *testing.T) {
 					Valid:          map[string]string{},
 					AllowedMethods: []string{"GET", "HEAD"},
 					Levels:         "2:2",
+					CacheKey:       "$scheme$proxy_host$request_uri",
 				},
 			},
 			msg: "cache policy with allowed methods and levels",
@@ -13248,6 +13472,7 @@ func TestGeneratePolicies(t *testing.T) {
 					ZoneSize:        "75m",
 					Valid:           map[string]string{},
 					CachePurgeAllow: []string{"192.168.1.0/24", "10.0.0.1"},
+					CacheKey:        "$scheme$proxy_host$request_uri",
 				},
 			},
 			msg: "cache policy with purge allow IPs",
@@ -13280,6 +13505,7 @@ func TestGeneratePolicies(t *testing.T) {
 					ZoneSize: "15m",
 					Time:     "45m",
 					Valid:    map[string]string{},
+					CacheKey: "$scheme$proxy_host$request_uri",
 				},
 			},
 			msg: "implicit cache policy reference",
diff --git a/pkg/apis/configuration/validation/policy_test.go b/pkg/apis/configuration/validation/policy_test.go
index 0ba5667ed..fc47d18c0 100644
--- a/pkg/apis/configuration/validation/policy_test.go
+++ b/pkg/apis/configuration/validation/policy_test.go
@@ -2643,6 +2643,151 @@ func TestValidatePolicy_IsNotValidCachePolicy(t *testing.T) {
 			},
 			isPlus: false,
 		},
+		{
+			name: "cache policy with invalid minUses (zero)",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "minuses",
+						CacheZoneSize: "10m",
+						CacheMinUses:  intPtr(0),
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with invalid manager files (zero)",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "managerbad",
+						CacheZoneSize: "10m",
+						Manager: &v1.CacheManager{
+							Files:     intPtr(0),
+							Sleep:     "100ms",
+							Threshold: "500ms",
+						},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with invalid manager sleep format",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "managersleep",
+						CacheZoneSize: "10m",
+						Manager: &v1.CacheManager{
+							Files:     intPtr(100),
+							Sleep:     "invalid",
+							Threshold: "500ms",
+						},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with invalid manager threshold format",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "managerthreshold",
+						CacheZoneSize: "10m",
+						Manager: &v1.CacheManager{
+							Files:     intPtr(100),
+							Sleep:     "100ms",
+							Threshold: "bad-time",
+						},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with invalid lock timeout format",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "locktimeout",
+						CacheZoneSize: "10m",
+						Lock: &v1.CacheLock{
+							Enable:  true,
+							Timeout: "invalid-timeout",
+						},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with invalid inactive format",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "inactive",
+						CacheZoneSize: "10m",
+						Inactive:      "bad-duration",
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with invalid max size format",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "maxsize",
+						CacheZoneSize: "10m",
+						MaxSize:       "invalid-size",
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with invalid cacheUseStale parameter",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "invalidstaleparameter",
+						CacheZoneSize: "10m",
+						CacheUseStale: []string{"error", "invalid_param", "timeout"},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with duplicate cacheUseStale parameters",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "duplicatestale",
+						CacheZoneSize: "10m",
+						CacheUseStale: []string{"error", "timeout", "error"},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with invalid cache key ending with $",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "invalidkey",
+						CacheZoneSize: "10m",
+						CacheKey:      "$scheme$host$request_uri$", // Invalid: ends with $
+					},
+				},
+			},
+			isPlus: false,
+		},
 	}
 
 	for _, tc := range tt {
@@ -2772,6 +2917,181 @@ func TestValidatePolicy_IsValidCachePolicy(t *testing.T) {
 			},
 			isPlus: false,
 		},
+		{
+			name: "cache policy with extended cache key configuration",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "extended",
+						CacheZoneSize: "20m",
+						CacheKey:      "${scheme}${host}${request_uri}${args}",
+						CacheMinUses:  intPtr(5),
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with full manager configuration",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "managercache",
+						CacheZoneSize: "30m",
+						Manager: &v1.CacheManager{
+							Files:     intPtr(200),
+							Sleep:     "100ms",
+							Threshold: "500ms",
+						},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with lock configuration",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "lockcache",
+						CacheZoneSize: "15m",
+						Lock: &v1.CacheLock{
+							Enable:  true,
+							Timeout: "30s",
+						},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with conditions configuration",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "conditioncache",
+						CacheZoneSize: "25m",
+						Conditions: &v1.CacheConditions{
+							NoCache: []string{"$cookie_nocache", "$arg_nocache"},
+							Bypass:  []string{"$http_pragma", "$http_authorization"},
+						},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with all extended fields",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "fullextended",
+						CacheZoneSize: "100m",
+						CacheKey:      "${scheme}${host}${request_uri}",
+						CacheMinUses:  intPtr(3),
+						UseTempPath:   false,
+						MaxSize:       "2g",
+						Inactive:      "7d",
+						Manager: &v1.CacheManager{
+							Files:     intPtr(500),
+							Sleep:     "200ms",
+							Threshold: "1s",
+						},
+						Lock: &v1.CacheLock{
+							Enable:  true,
+							Timeout: "60s",
+						},
+						Conditions: &v1.CacheConditions{
+							NoCache: []string{"$cookie_admin"},
+							Bypass:  []string{"$http_cache_control"},
+						},
+						CacheBackgroundUpdate: true,
+						CacheRevalidate:       true,
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with valid cacheUseStale parameters",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "validstale",
+						CacheZoneSize: "10m",
+						CacheUseStale: []string{"error", "timeout", "http_502"},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with updating parameter (cache specific)",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "staleupdate",
+						CacheZoneSize: "10m",
+						CacheUseStale: []string{"error", "timeout", "updating"},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with all valid cacheUseStale parameters",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "stallall",
+						CacheZoneSize: "10m",
+						CacheUseStale: []string{"error", "timeout", "invalid_header", "updating", "http_500", "http_502", "http_503", "http_504"},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with empty cacheUseStale (should be valid)",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "emptystale",
+						CacheZoneSize: "10m",
+						CacheUseStale: []string{},
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with unbraced cache key variables",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "unbraced",
+						CacheZoneSize: "10m",
+						CacheKey:      "$scheme$host$request_uri", // Test unbraced NGINX variable format
+						Time:          "15m",
+					},
+				},
+			},
+			isPlus: false,
+		},
+		{
+			name: "cache policy with mixed braced and unbraced cache key variables",
+			policy: &v1.Policy{
+				Spec: v1.PolicySpec{
+					Cache: &v1.Cache{
+						CacheZoneName: "mixed",
+						CacheZoneSize: "10m",
+						CacheKey:      "$scheme${host}$request_uri", // Test mixed format
+						Time:          "20m",
+					},
+				},
+			},
+			isPlus: false,
+		},
 	}
 
 	for _, tc := range tt {
diff --git a/tests/data/cache-policy/policies/cache-policy-advanced.yaml b/tests/data/cache-policy/policies/cache-policy-advanced.yaml
index 2e7da75f9..f67c9cf7d 100644
--- a/tests/data/cache-policy/policies/cache-policy-advanced.yaml
+++ b/tests/data/cache-policy/policies/cache-policy-advanced.yaml
@@ -11,3 +11,26 @@ spec:
     time: "2h"
     overrideUpstreamCache: true
     levels: "2:2"
+    inactive: "60m"
+    useTempPath: false
+    maxSize: "10g"
+    minFree: "1g"
+    manager:
+      files: 100
+      sleep: "50ms"
+      threshold: "200ms"
+
+    cacheKey: "$scheme$host$request_uri$request_method"
+    cacheUseStale: [ "error", "timeout", "updating", "http_500" ]
+    cacheRevalidate: true
+    cacheBackgroundUpdate: true
+    cacheMinUses: 1
+
+    lock:
+      enable: true
+      timeout: "5s"
+      age: "30s"
+
+    conditions:
+      noCache: [ "$cookie_nocache", "$arg_nocache" ]
+      bypass: [ "$http_authorization" ]
diff --git a/tests/suite/test_cache_policies_vs.py b/tests/suite/test_cache_policies_vs.py
index 4ac724026..ef5d7624c 100644
--- a/tests/suite/test_cache_policies_vs.py
+++ b/tests/suite/test_cache_policies_vs.py
@@ -157,11 +157,11 @@ class TestCachePolicies:
         resp_2 = requests.get(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host})
         cache_status_2 = resp_2.headers.get("X-Cache-Status")
 
-        # Test cache behavior for POST requests
+        # Test cache behavior for POST requests (cached separately due to $request_method in cache key)
         resp_3 = requests.post(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host})
         cache_status_3 = resp_3.headers.get("X-Cache-Status")
 
-        # Test cache behavior for HEAD requests
+        # Test cache behavior for HEAD requests (cached separately due to $request_method in cache key)
         resp_4 = requests.head(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host})
         cache_status_4 = resp_4.headers.get("X-Cache-Status")
 
@@ -179,11 +179,13 @@ class TestCachePolicies:
                 "Request ID:" in resp_1.text,
                 "Request ID:" in resp_2.text,
                 "Request ID:" in resp_3.text,
-                req_id_1.group(1) == req_id_2.group(1) == req_id_3.group(1),
+                req_id_1.group(1) == req_id_2.group(1),  # GET requests should share cache
+                req_id_1.group(1)
+                != req_id_3.group(1),  # POST should NOT share cache with GET due to $request_method in cacheKey
                 cache_status_1 in ["MISS", "EXPIRED", None],
-                cache_status_2 == "HIT",
-                cache_status_3 == "HIT",
-                cache_status_4 == "HIT",
+                cache_status_2 == "HIT",  # Second GET should be cache hit
+                cache_status_3 in ["MISS", "EXPIRED", None],  # First POST should be cache miss (different key)
+                cache_status_4 in ["MISS", "EXPIRED", None],  # HEAD should also be cache miss (different method)
             ]
         )
 
diff --git a/tests/suite/test_cache_policies_vsr.py b/tests/suite/test_cache_policies_vsr.py
index 1947a0077..7b6343140 100644
--- a/tests/suite/test_cache_policies_vsr.py
+++ b/tests/suite/test_cache_policies_vsr.py
@@ -169,14 +169,14 @@ class TestCachePoliciesVSR:
         cache_status_2 = resp_2.headers.get("X-Cache-Status")
         print(f"Cache status for second GET request: {cache_status_2}")
 
-        # Test cache behavior for POST requests (should be cached with advanced policy)
+        # Test cache behavior for POST requests (cached separately due to $request_method in cache key)
         resp_3 = requests.post(
             f"{req_url}{v_s_route_setup.route_m.paths[0]}", headers={"host": v_s_route_setup.vs_host}
         )
         cache_status_3 = resp_3.headers.get("X-Cache-Status")
         print(f"Cache status for first POST request: {cache_status_3}")
 
-        # Test cache behavior for HEAD requests
+        # Test cache behavior for HEAD requests (cached separately due to $request_method in cache key)
         resp_4 = requests.head(
             f"{req_url}{v_s_route_setup.route_m.paths[0]}", headers={"host": v_s_route_setup.vs_host}
         )
@@ -197,11 +197,12 @@ class TestCachePoliciesVSR:
                 "Request ID:" in resp_1.text,
                 "Request ID:" in resp_2.text,
                 "Request ID:" in resp_3.text,
-                req_id_1.group(1) == req_id_2.group(1) == req_id_3.group(1),
+                req_id_1.group(1) == req_id_2.group(1),  # GET requests should share cache
+                req_id_1.group(1) != req_id_3.group(1),  # POST has different cache key due to $request_method
                 cache_status_1 in ["MISS", "EXPIRED", None],
-                cache_status_2 == "HIT",
-                cache_status_3 == "HIT",
-                cache_status_4 == "HIT",
+                cache_status_2 == "HIT",  # Second GET should be cache hit
+                cache_status_3 in ["MISS", "EXPIRED", None],  # First POST should be cache miss
+                cache_status_4 in ["MISS", "EXPIRED", None],  # HEAD should also be cache miss (different method)
             ]
         )