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
getorlistsecrets 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.