Skip to content
Back to Blog
Security11 min read

Kubernetes Security Best Practices: 12 Fixes [2026]

Close real attack vectors in your Kubernetes clusters with actionable fixes for RBAC, container supply-chain risks, network policies, and secrets management.

Written by Abdul AbrorTechnical Hosting Support Engineer
Kubernetes Security Best Practices: 12 Fixes [2026]
On this page

Most Kubernetes breaches start with a preventable misconfiguration. RBAC rules too permissive, secrets in plain text, network policies left wide open. The defaults let you spin up clusters fast, but they don't secure production workloads.

This checklist covers twelve fixes that close real attack vectors I've seen in support incidents and cluster audits. Each one addresses a specific risk—overprivileged service accounts, unsigned container images, missing egress controls—and gives you the concrete commands or manifests to fix it.

1. Lock down RBAC with least-privilege roles

Default service accounts often get cluster-admin or namespace-admin rights because someone needed quick access during setup and never tightened it. Attackers who compromise a pod inherit those permissions.

Start by auditing what roles are bound to what service accounts:

kubectl get rolebindings,clusterrolebindings --all-namespaces \
  -o json | jq '.items[] | select(.subjects[]?.kind=="ServiceAccount")'

For each binding, ask: does this pod actually need to list secrets cluster-wide, or can it work with read access to a single ConfigMap?

Create narrow roles. A logging sidecar doesn't need write access to anything:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: log-reader
  namespace: production
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list"]

Bind it to a dedicated service account, never default.

2. Never run pods as root

Containers that run as UID 0 can write to the host filesystem if a volume is misconfigured or a kernel vulnerability appears. The fix is a one-line securityContext in your pod spec:

securityContext:
  runAsNonRoot: true
  runAsUser: 10001
  fsGroup: 10001

Pod Security Standards enforce this at the namespace level. Apply the restricted policy to every namespace except where you have a documented exception:

kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted

If a deployment breaks, fix the container image rather than weakening the policy.

3. Drop unnecessary capabilities

Linux capabilities split root privileges into granular permissions. Most containers don't need any. Drop them all by default:

securityContext:
  capabilities:
    drop:
    - ALL

If a container genuinely needs NET_BIND_SERVICE to bind to port 80, add only that capability back. Never add SYS_ADMIN or SYS_PTRACE—those are shortcuts to container escape.

4. Set read-only root filesystems

An attacker who gets code execution in a pod will try to write a backdoor to disk. A read-only root filesystem blocks that:

securityContext:
  readOnlyRootFilesystem: true

You'll need to mount writable emptyDir volumes for /tmp and any directories the application writes to:

volumeMounts:
- name: tmp
  mountPath: /tmp
volumes:
- name: tmp
  emptyDir: {}

This stops an entire class of persistence techniques.

5. Verify container image signatures

You can't trust a container image just because it came from your registry. An attacker with registry credentials can push a malicious tag, or a compromised CI pipeline can do it accidentally.

Sign images at build time with Cosign or Sigstore:

cosign sign --key cosign.key myregistry.example.com/app:v1.2.3

Then enforce signature verification at admission time. Projects like Kyverno and Sigstore Policy Controller reject unsigned images before they reach a node:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  validationFailureAction: enforce
  rules:
  - name: verify-signature
    match:
      resources:
        kinds:
        - Pod
    verifyImages:
    - image: "myregistry.example.com/*"
      key: |-
        -----BEGIN PUBLIC KEY-----
        ...
        -----END PUBLIC KEY-----

This blocks supply-chain attacks at the front door.

6. Scan images for known vulnerabilities

Signed images can still contain outdated libraries with public CVEs. Run a scanner like Trivy in your CI pipeline and fail the build on high-severity findings:

trivy image --severity HIGH,CRITICAL \
  --exit-code 1 myregistry.example.com/app:v1.2.3

Schedule the same scan against running images weekly, because new vulnerabilities appear after deployment. Your registry can often run these scans automatically.

Don't ignore the reports. Rebuild images with patched base layers.

7. Enforce network policies on every namespace

By default, any pod can talk to any other pod in the cluster. An attacker who compromises a frontend can pivot to your database without a firewall in the way.

Network policies are your firewall. Start with a default-deny rule:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Then add specific allow rules for each communication path:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080

Most breaches I've investigated could have been contained by a single network policy.

8. Restrict egress to known endpoints

Ingress policies get attention, but egress matters just as much. A compromised pod will try to exfiltrate data or download additional payloads.

Allow only the external services your application needs:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-egress-to-api
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          name: kube-system
    ports:
    - protocol: UDP
      port: 53
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - protocol: TCP
      port: 5432
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
    ports:
    - protocol: TCP
      port: 443
  policyTypes:
  - Egress

DNS to kube-system, database in the same namespace, and HTTPS to the internet. Nothing else.

9. Encrypt secrets at rest

Kubernetes stores secrets in etcd, and by default etcd writes them to disk in base64—not encrypted. Anyone with filesystem access to an etcd node can read every secret in the cluster.

Enable encryption at rest in your API server configuration:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: <base64-encoded-32-byte-key>
  - identity: {}

Pass it to the API server with --encryption-provider-config. Rotate the key periodically and re-encrypt existing secrets:

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

ManagedKubernetes services often enable this by default, but verify.

10. Use external secret stores

Even encrypted, secrets in etcd are still in the cluster. Developers with kubectl access can read them, and a compromised API server exposes them.

External secret stores—HashiCorp Vault, AWS Secrets Manager, Azure Key Vault—keep secrets outside the cluster and inject them at pod start. The External Secrets Operator syncs them automatically:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-password
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: db-password
  data:
  - secretKey: password
    remoteRef:
      key: secret/data/db
      property: password

The operator fetches the secret from Vault and creates a Kubernetes secret. Pods mount it like any other secret, but it never lives permanently in etcd.

Audit logs in the external store give you a second source of truth for who accessed what.

11. Enable audit logging and monitor for anomalies

Kubernetes audit logs record every API request—who created that pod, who read those secrets, who deleted that deployment. Without them, you're flying blind during an incident.

Enable audit logging in your API server:

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
  resources:
  - group: ""
    resources: ["secrets", "configmaps"]
- level: RequestResponse
  verbs: ["create", "update", "patch", "delete"]

Ship the logs to a separate system—your SIEM, a log aggregator, anything outside the cluster. If an attacker gets cluster-admin, they can delete the logs.

Set alerts for suspicious patterns: new ClusterRoleBindings, secrets accessed by service accounts that never needed them before, exec commands into production pods.

12. Harden node security with CIS benchmarks

Pods run on nodes, and nodes run Linux. A weak node configuration undermines everything else.

The CIS Kubernetes Benchmark spells out hundreds of hardening steps—disable anonymous API access, restrict kubelet permissions, enable kernel protections. Run kube-bench to audit your nodes:

kube-bench run --targets node

Fix the failures it finds. Common ones: kubelet API allowing anonymous requests, read-only port still enabled, file permissions on config files too open.

Managed services handle many of these for you, but you still control kubelet configuration and OS-level settings.

What to apply first

You don't need to implement all twelve at once. Start with RBAC and network policies—they stop lateral movement. Add Pod Security Standards next to block privilege escalation. Then tackle secrets management and image signing.

Most incidents I see could have been stopped by three things: tighter RBAC, a default-deny network policy, and not running as root. Fix those this week. The rest can follow.

FAQ

Do network policies work with all CNI plugins?
No. Calico, Cilium, and Weave support them fully. Flannel does not. Check your CNI documentation before relying on network policies.

Can I enforce Pod Security Standards on existing clusters without breaking workloads?
Yes. Start in audit or warn mode to see which pods would fail, fix them, then switch to enforce.

Should I scan images before every deployment or just at build time?
Both. Build-time scans catch known issues early. Runtime scans catch new vulnerabilities disclosed after deployment.

Does encrypting secrets at rest protect against cluster-admin users?
No. Cluster-admin can still read secrets through the API. Use external secret stores to protect against compromised cluster access.

How often should I rotate encryption keys for etcd?
Every 90 days minimum, or immediately if you suspect a key was exposed.

Start with your attack surface

Run kubectl get pods --all-namespaces -o json and count how many run as root. Check which service accounts have cluster-wide permissions. List your namespaces and see which ones lack network policies.

Those three queries show you where the gaps are. Fix the biggest risks first, then work down the list.