Running your own Kubernetes cluster on VPS or dedicated servers, with k3s or kubeadm, gives you a lot of control, and puts the security responsibility squarely on you. Kubernetes isn’t insecure by design, but its defaults favour getting started over locking down. These are the basics every small self-managed cluster should cover.

1. Protect the API server

The API server (port 6443) is the keys to the cluster.

  • Don’t expose 6443 to the whole internet. Restrict it with the firewall to admin IPs, a VPN or a bastion. See zero trust networking.
  • Keep anonymous auth disabled, and never use static tokens with broad rights.
  • Protect kubeconfig files like root SSH keys. The default admin kubeconfig has cluster-admin rights. Create per-user credentials with limited roles instead of sharing it.
  • The kubelet API (10250) and etcd (2379–2380) must only be reachable from cluster nodes.
sudo ss -tlnp | grep -E '6443|10250|2379'

Check the bind addresses, and verify from outside with nmap.

2. RBAC: least privilege for people and workloads

  • Give users and CI pipelines namespaced Roles, not cluster-admin.
  • Each application gets its own ServiceAccount. Disable token automounting where pods don’t talk to the API:
apiVersion: v1
kind: ServiceAccount
metadata:
  name: web
  namespace: shop
automountServiceAccountToken: false
  • Audit who can do what:
kubectl auth can-i --list --as=system:serviceaccount:shop:web -n shop
kubectl get clusterrolebindings -o wide | grep -i cluster-admin

See least privilege.

3. Pod Security Standards

Kubernetes has built-in Pod Security Admission with three levels: privileged, baseline and restricted. Enforce baseline (or restricted) on application namespaces:

kubectl label namespace shop pod-security.kubernetes.io/enforce=baseline \
  pod-security.kubernetes.io/warn=restricted

And write pods that meet restricted:

securityContext:
  runAsNonRoot: true
  runAsUser: 10001
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop: ["ALL"]
  seccompProfile:
    type: RuntimeDefault

No privileged: true, hostNetwork, hostPID or hostPath mounts for normal apps. These are the same principles as in Docker security.

4. Network policies

By default, every pod can talk to every other pod, across namespaces. A compromised frontend can reach your database directly. NetworkPolicies restrict this, but they need a CNI that enforces them (Calico, Cilium; k3s’s default setup includes a policy controller).

Default-deny per namespace, then allow what’s needed:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
  namespace: shop
spec:
  podSelector: {}
  policyTypes: ["Ingress"]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-from-api
  namespace: shop
spec:
  podSelector:
    matchLabels: { app: postgres }
  ingress:
    - from:
        - podSelector:
            matchLabels: { app: api }
      ports:
        - port: 5432

5. Secrets

  • Kubernetes Secrets are only base64-encoded. Enable encryption at rest for etcd (k3s: --secrets-encryption).
  • Restrict who can get or list secrets via RBAC.
  • Don’t commit secret manifests to Git in plain text. Use Sealed Secrets, SOPS, or an external secrets operator backed by a vault. See secrets management.

6. Images and supply chain

  • Minimal, pinned images (by tag and ideally digest). No latest.
  • Scan images in CI (Trivy, Grype), and block critical vulnerabilities.
  • Pull only from registries you trust. Admission policies (Kyverno, OPA Gatekeeper) can enforce this, along with “no privileged pods”, required labels, resource limits and so on.

7. Resource limits

Set requests and limits on every container, plus ResourceQuotas per namespace, so a misbehaving or attacked workload can’t starve the node.

8. Harden the nodes

The cluster is only as secure as its servers:

  • key-only SSH, firewall, automatic security updates. See Linux server hardening;
  • a minimal OS install, with only the necessary services;
  • runtime monitoring (Falco detects suspicious container behaviour, such as shells in containers and unexpected binaries).

9. Audit logs and monitoring

Enable API server audit logging (who did what via the API), and ship it, plus node logs, to central storage. Alert on new cluster-admin bindings, exec into pods in production, and secret reads by unusual accounts. See server security monitoring.

10. Upgrade regularly

Kubernetes minor versions get about 14 months of patches. Stay within the supported versions, and upgrade the control plane and nodes regularly (k3s: system-upgrade-controller; kubeadm: kubeadm upgrade). Also keep add-ons (ingress controllers, CNI, cert-manager) updated, since they’re frequent targets. See kubeadm and k3s.

Quick checklist

Area Check
API server Not public. Per-user credentials
RBAC No unnecessary cluster-admin. Per-app ServiceAccounts
Pods Pod Security baseline or higher, non-root, no privileged
Network Default-deny NetworkPolicies
Secrets Encrypted at rest, not in Git
Images Pinned, scanned
Nodes Hardened, patched
Versions Supported Kubernetes release

Related: kubectl commands and Helm.

  • Kubernetes
  • Security
  • DevOps