Skip to main content
Calico Enterprise 3.23 (latest) documentation

Recommend policies for hosts

Big picture

Extend policy recommendations so Calico Enterprise automatically generates staged global network policies for hosts in your cluster, based on observed flow logs.

Value

Implementing least-privileged network policy for hosts is time-consuming and error-prone to do by hand. Calico Enterprise analyzes flow logs from hosts and creates a staged global network policy per host that captures the traffic the host actually sends and receives. You can review the staged policies, confirm they match real traffic, and then promote them to enforced global network policies when you're ready.

Before you begin

Supported

Unsupported

  • Management and managed-cluster topologies.
  • The web console. Enabling the feature, and accepting, dismissing, and promoting the generated recommendations are done with kubectl and calicoctl. Generated staged policies still appear in the Policies board alongside other policies.

Required RBAC

To enable and use host-policy recommendations, you must have the tigera-network-admin role, or permissions to update, patch, get, list, watch the following projectcalico.org resources:

  • policyrecommendationscopes
  • tiers
  • stagedglobalnetworkpolicies
  • tier.stagedglobalnetworkpolicies
  • globalnetworkpolicies
  • tier.globalnetworkpolicies
  • globalnetworksets
  • hostendpoints

In particular, you need access to the hostendpoint-isolation tier and to the staged and enforced global network policies in that tier.

Prerequisites

  • Each host you want recommendations for must run calico-node and be represented as a HostEndpoint.
  • For private-network and public-domain rule recommendations, Felix DNS snooping must be able to resolve the host's active DNS servers so that flow logs include dest_domains. If your hosts don't pick up the cluster's default DNS resolver, set dnsTrustedServers on calico-node accordingly.

How to

Enable host-policy recommendations

Enable host-policy recommendations by setting hostEndpointSpec.recommendationStatus to Enabled on the PolicyRecommendationScope resource. When the selector is omitted, every HostEndpoint in the cluster is eligible.

kubectl patch policyrecommendationscope default --type=merge -p '{
"spec": {
"hostEndpointSpec": {
"recommendationStatus": "Enabled"
}
}
}'
note

Host recommendations and namespace recommendations are configured independently, through hostEndpointSpec and namespaceSpec respectively. Enabling one does not enable the other.

By default, generated policies are placed in the hostendpoint-isolation tier. The engine creates this tier automatically if it doesn't exist. To use a different tier, set hostEndpointSpec.tierName to the name of a tier you have already created.

kubectl get stagedglobalnetworkpolicies \
-l projectcalico.org/tier=hostendpoint-isolation

One staged global network policy is generated per HostEndpoint that matches the selector. Each recommended policy has one of three states you can set — Learn, Set, or Ignore — tracked by the projectcalico.org/spec.stagedAction label and spec.stagedAction field. See Manage the life cycle of recommendations.

Scope recommendations to specific hosts

By default, every host endpoint in the cluster is eligible for recommendations. To restrict the engine to a subset, set hostEndpointSpec.selector to a Calico label-selector expression that matches the labels of the host endpoints you want to cover.

# Recommend policies only for host endpoints labelled environment=prod.
kubectl patch policyrecommendationscope default --type=merge --patch-file=/dev/stdin <<'EOF'
spec:
hostEndpointSpec:
recommendationStatus: Enabled
selector: environment == 'prod'
EOF

This table shows common selector patterns for host endpoint policy recommendations:

GoalExpression
Match all host endpointsall() (or omit selector)
Match a single label valuerole == 'edge'
Match any of several valuesrole in { 'edge', 'gateway' }
Match host endpoints that carry a given labelhas(zone)
Combine conditions with boolean operatorsenvironment == 'prod' && !has(quarantined)

For the full operator reference — comparison, set membership, substring, and boolean composition — see label selectors.

Tune global settings

Two top-level fields on the PolicyRecommendationScope are commonly adjusted to fit a cluster's traffic patterns:

caution

stabilizationPeriod and interval are top-level fields on the PolicyRecommendationScope, not scoped under hostEndpointSpec. Changes you make here apply to both namespace and host policy recommendations. If namespace recommendations are also enabled on this cluster, make sure the new values work for both.

  • stabilizationPeriod — how long a staged recommendation must remain unchanged before it's considered stable and ready to enforce. The default (10m) works for hosts with frequent, representative traffic. For hosts whose normal traffic takes longer to exercise every path, extend the period so the engine has time to observe a full cycle.
  • interval — how often the engine processes new flow logs and refines recommendations. The default is 2m30s.
# Extend the stabilization period to 30 minutes.
kubectl patch policyrecommendationscope default --type=merge \
-p '{"spec":{"stabilizationPeriod":"30m"}}'

# Refine recommendations less frequently.
kubectl patch policyrecommendationscope default --type=merge \
-p '{"spec":{"interval":"5m"}}'
tip

Review these settings soon after enabling host-policy recommendations and adjust them based on how quickly your hosts' traffic stabilizes. You can always revisit them later.

The per-spec tierName and selector fields are also set on the same resource. See PolicyRecommendationScope for the full list.

Manage the life cycle of recommendations

Each staged global network policy generated by the engine is in one of three states, which you control through the projectcalico.org/spec.stagedAction label and the spec.stagedAction field:

StateMeaning
LearnEngine is actively refining the recommendation from new flow logs.
SetRecommendation has been accepted; the engine no longer modifies it.
IgnoreRecommendation is dismissed; the engine stops updating it.

This section covers the transitions between those states and the operations most commonly performed on recommendations.

Activate a recommendation

Accepting a recommendation transitions it from Learn to Set, which stops the engine from further modifying it. This is the prerequisite for enforcing a recommendation.

Accept a single recommendation:

kubectl patch stagedglobalnetworkpolicy.projectcalico.org <name> --type=merge \
-p '{"metadata":{"labels":{"projectcalico.org/spec.stagedAction":"Set"}},"spec":{"stagedAction":"Set"}}'

Accept every recommendation still in the learning stage:

kubectl get stagedglobalnetworkpolicies.projectcalico.org \
-l projectcalico.org/tier=hostendpoint-isolation,projectcalico.org/spec.stagedAction=Learn \
-o name \
| xargs -r -n1 kubectl patch --type=merge \
-p '{"metadata":{"labels":{"projectcalico.org/spec.stagedAction":"Set"}},"spec":{"stagedAction":"Set"}}'

Enforce a recommendation

Enforcing a recommendation converts an accepted staged global network policy into an enforced GlobalNetworkPolicy, which begins to affect traffic. Always confirm the recommendation is matching real traffic before enforcing it.

Enforce a single recommendation:

kubectl get stagedglobalnetworkpolicies.projectcalico.org <name> -o yaml \
| yq -y '
.apiVersion = "projectcalico.org/v3"
| .kind = "GlobalNetworkPolicy"
| .metadata = {"name": .metadata.name, "labels": {"projectcalico.org/tier": .metadata.labels."projectcalico.org/tier"}}
| del(.spec.stagedAction)
| del(.spec.ingress[]?.metadata, .spec.egress[]?.metadata)
' \
| kubectl apply -f -

Enforce every accepted recommendation:

kubectl get stagedglobalnetworkpolicies.projectcalico.org \
-l projectcalico.org/tier=hostendpoint-isolation,projectcalico.org/spec.stagedAction=Set \
-o yaml \
| yq -y '
(.items[] |= (
.apiVersion = "projectcalico.org/v3"
| .kind = "GlobalNetworkPolicy"
| .metadata = {"name": .metadata.name, "labels": {"projectcalico.org/tier": .metadata.labels."projectcalico.org/tier"}}
| del(.spec.stagedAction)
| del(.spec.ingress[]?.metadata, .spec.egress[]?.metadata)
))
' \
| kubectl apply -f -
note

Enforcing a recommendation does not delete the original staged policy. If you no longer need it, delete it explicitly after you've verified the enforced policy is working as expected.

Stop policy recommendation updates for a HostEndpoint

To stop updates for a host, dismiss the recommendation by setting its stagedAction to Ignore:

kubectl patch stagedglobalnetworkpolicy.projectcalico.org <name> --type=merge \
-p '{"metadata":{"labels":{"projectcalico.org/spec.stagedAction":"Ignore"}},"spec":{"stagedAction":"Ignore"}}'

Relearn an activated recommendation

To have the engine regenerate a recommendation from scratch — useful when the host's traffic pattern has changed — transition the staged policy back to Learn. The engine re-evaluates flow logs and rebuilds the recommendation on the next processing cycle.

kubectl patch stagedglobalnetworkpolicy.projectcalico.org <name> --type=merge \
-p '{"metadata":{"labels":{"projectcalico.org/spec.stagedAction":"Learn"}},"spec":{"stagedAction":"Learn"}}'

Rerun policy recommendations for an enforced policy

Once a recommendation has been enforced, the engine no longer refines it. To have the engine generate a new recommendation for a host that already has an enforced policy, delete the enforced GlobalNetworkPolicy:

kubectl delete globalnetworkpolicy.projectcalico.org <name>

On the next processing cycle, the engine creates a fresh staged recommendation in Learn.

Track recommendation readiness

In addition to the stagedAction lifecycle described above (which you control), the engine reports its own view of each recommendation through the policyrecommendation.tigera.io/status annotation on the staged global network policy. Use it to know when a recommendation has stopped changing long enough to be ready to enforce.

This table describes the values the engine assigns to the policyrecommendation.tigera.io/status annotation:

ValueMeaning
LearningEngine is still actively adding or modifying rules from flow logs. This is the initial value and is reset whenever the engine changes the recommendation.
StabilizingEngine has not modified the recommendation for longer than 2 × interval, but no more than stabilizationPeriod. Rules look settled but the engine is still watching.
StableEngine has not modified the recommendation for longer than stabilizationPeriod. The recommendation is considered ready to enforce.

The status transitions are driven by the time since the engine last modified the recommendation, not by elapsed time alone:

  • A host that suddenly sends new traffic during a Stabilizing period returns to Learning while the engine incorporates the new rules.
  • The stabilizationPeriod and interval values in Tune global settings directly control how quickly a recommendation can reach Stable.

Read the annotation with kubectl:

kubectl get stagedglobalnetworkpolicy.projectcalico.org <name> \
-o jsonpath='{.metadata.annotations.policyrecommendation\.tigera\.io/status}{"\n"}'

List the status of every host-policy recommendation at once:

kubectl get stagedglobalnetworkpolicies.projectcalico.org \
-l projectcalico.org/tier=hostendpoint-isolation \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations.policyrecommendation\.tigera\.io/status}{"\n"}{end}'
tip

Wait for Stable before accepting and enforcing a recommendation. Accepting a recommendation while it is still learning or stabilizing pins the current — possibly incomplete — set of rules and stops the engine from refining it further.

Confirm a recommendation is matching real traffic

Use calicoctl get with --show-policy-activities to check when the recommendation engine last evaluated each staged policy against live flow logs. Recent timestamps mean the policy is being actively exercised by observed traffic — useful reassurance before you enforce a recommendation.

Query a single recommendation:

calicoctl get stagedglobalnetworkpolicy <name> --show-policy-activities

Or list every staged global network policy — host-policy recommendations show up in the hostendpoint-isolation tier and can be identified by the TIER column:

calicoctl get stagedglobalnetworkpolicies --show-policy-activities

To scope the output directly to host-policy recommendations, use kubectl get -o name to resolve names in the tier and pass each one to calicoctl get:

kubectl get stagedglobalnetworkpolicies.projectcalico.org \
-l projectcalico.org/tier=hostendpoint-isolation -o name \
| sed 's|.*/||' \
| xargs -r -I{} calicoctl get stagedglobalnetworkpolicy {} --show-policy-activities

Example output:

NAME TIER LAST EVALUATED LAST EVALUATED (ANY GEN) LAST EVALUATED GEN
hostendpoint-isolation.host-a-vfzgh hostendpoint-isolation 2026-04-20T14:32:01-07:00 2026-04-20T14:32:01-07:00 4
hostendpoint-isolation.host-b-k9x2p hostendpoint-isolation NOT YET 2026-04-20T14:20:15-07:00 2
hostendpoint-isolation.host-c-pq4tm hostendpoint-isolation NOT YET NEVER N/A

The three extra columns mean:

  • LAST EVALUATED — the most recent time the current generation of the policy was evaluated against a flow log. A recent timestamp here is the strongest signal that the policy, as it currently stands, is matching live traffic.
  • LAST EVALUATED (ANY GEN) — the most recent time any generation (current or past) was evaluated. If LAST EVALUATED is NOT YET but this column has a recent timestamp, a previous version of the policy was matching traffic and the current version has not been evaluated yet — typical right after the engine refines the recommendation.
  • LAST EVALUATED GEN — which generation the timestamp in LAST EVALUATED (ANY GEN) refers to. Compare it to metadata.generation on the staged policy to know how recent that evaluation was.

NOT YET means the current generation has not yet been evaluated. NEVER means no generation of this policy has been evaluated against traffic yet. NEVER is typically a transient state immediately after the engine first generates a recommendation, before the policy-activity pipeline has recorded any match. A host that has never sent traffic will not have a staged recommendation at all — the engine creates a staged policy only after observing the first flow that matches the HostEndpoint.

Disable host-policy recommendations

kubectl patch policyrecommendationscope default --type=merge \
-p '{"spec":{"hostEndpointSpec":{"recommendationStatus":"Disabled"}}}'

Disabling stops the engine from generating or refining host recommendations. Existing staged and enforced policies are not removed.

Limitations

  • Host-policy recommendations are only available on standalone clusters.
  • The feature has no web console surface for enabling, accepting, dismissing, or promoting recommendations — all workflows are CLI-driven.
  • Private-network and public-domain rule recommendations depend on DNS snooping producing dest_domains in flow logs. If DNS trusted servers aren't configured correctly on the host, those rules are not generated.

Troubleshoot

View policy-recommendation logs:

kubectl logs -n calico-system -l k8s-app=tigera-policy-recommendation

Problem: No private-network or public-domain rules are generated.

Solution: Check that dest_domains is populated in flow logs. Missing dest_domains is usually caused by one of:

  • DNS trusted servers are not configured correctly on the host, so Felix DNS snooping cannot observe name resolutions. See the DNS prerequisite above.
  • The FelixConfiguration field flowLogsDestDomainsByClient is set to true. When enabled, dest_domains is attributed to the client flow only and is not populated on the server-side flow log the recommendation engine relies on. Set flowLogsDestDomainsByClient to false (the default) if you need host-policy recommendations to generate private-network and public-domain rules.

Problem: No recommendations appear after enabling the feature.

Solution: Confirm that your HostEndpoints match the selector (or that the selector is omitted so all hosts are eligible), and that flows for the host are visible in Service Graph.

Additional resources