Error from server (Forbidden): User cannot list resource
Fix "Forbidden: User cannot list resource": confirm with kubectl auth can-i, then add a Role and RoleBinding for the exact verb and apiGroup, not cluster-admin.
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:api" cannot list resource "pods" i
Incident Overview
KubernetesKubernetes
Fix "Forbidden: User cannot list resource": confirm with kubectl auth can-i, then add a Role and RoleBinding for the exact verb and apiGroup, not cluster-admin.
medium fix·6 min read
By Vishvesh Patel·DevOps Engineer·Updated 21 September 2026
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:api" cannot list resource "pods" in API group "" in the namespace "production" Error from server (Forbidden): deployments.apps is forbidden: User "dev@acme.com" cannot create resource "deployments" in API group "apps" at the cluster scope error: failed to create clusterrolebinding: clusterroles.rbac.authorization.k8s.io "admin" is forbidden: attempt to grant extra privileges
Do this first3 steps
Run these in order. Each one tells you what its output means before you change anything.
• Ask the API server directly whether the identity is allowed
This runs the same authorization check the real request runs, so a "no" here confirms RBAC rather than a bug elsewhere. --as requires impersonation rights, which cluster-admin has.
• List everything that identity can do
The output is the full effective permission set. Compare it against the verb and resource named in the error, remembering that get, list and watch are three separate verbs.
• Find the bindings that apply to it
No output means no binding exists for that subject, which is the most common cause. A binding that exists but is a RoleBinding in the wrong namespace is the second.
RBAC denied the request. The message is unusually informative, so read every field before changing anything:
Note the identity is in default and the request is against production. That cross-namespace shape is the most common cause of these.
kubectl auth can-i list pods --namespace production --as system:serviceaccount:default:api
Confirm it
This runs the real authorization check, so it is authoritative. --as needs impersonation rights, which cluster-admin has.
Then see everything the identity can do:
get and watch but not list. That is the whole bug, and it is easy to miss because the three feel like one thing. kubectl get pods (plural, no name) issues a list; kubectl get pod my-pod issues a get. A Role with get alone lets you fetch a named Pod and not enumerate them.
kubectl auth can-i list pods \ --namespace production \ --as system:serviceaccount:default:api
Find the bindings
Bound in default, and the request was against production. A RoleBinding only grants within its own namespace.
kubectl get rolebindings,clusterrolebindings -A -o json \
| jq -r '.items[]
| select(.subjects[]?.name=="api")
| "\(.kind) \(.metadata.namespace // "-") \(.metadata.name) -> \(.roleRef.kind)/\(.roleRef.name)"'The four rules that explain most failures
1. A RoleBinding is namespaced. It grants permission only in the namespace it lives in, regardless of which namespace the subject's ServiceAccount is in. To grant access in production, the RoleBinding must be in production.
2. A ClusterRole is only cluster-wide when bound by a ClusterRoleBinding. The same ClusterRole referenced from a RoleBinding grants its rules within that one namespace. This is genuinely useful: define pod-reader once as a ClusterRole, then bind it per namespace.
3. Verbs are exact. get, list and watch are three permissions. So are create, update, patch and delete. kubectl apply needs get, patch and create, which is why apply can fail while create alone works.
4. Subresources are separate resources. pods/log, pods/exec and pods/portforward each need their own rule. A Role with full access to pods still cannot run kubectl logs.
Grant what is missing
Same-namespace ServiceAccount:
The RoleBinding is in production and the subject names its own namespace. Both parts matter.
Note that roleRef is immutable. Changing which Role a binding points at means deleting and recreating it.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: production
name: api-pod-reader
subjects:
- kind: ServiceAccount
name: api
namespace: default # the account lives elsewhere
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioGetting the apiGroup right
This is the other frequent mistake. The group in the error message is the group your rule needs:
kubectl api-resources prints the group for everything, which beats guessing.
The Pod is not using the ServiceAccount you think
A Pod with no serviceAccountName uses default, which has essentially no permissions.
An existing Pod cannot be changed; the field is immutable, so the Deployment has to be updated and the Pod recreated.
kubectl get pod my-pod -o jsonpath='{.spec.serviceAccountName}'"attempt to grant extra privileges"
This is privilege escalation prevention. You cannot grant permissions you do not hold yourself. It stops a namespace admin writing themselves a cluster-admin binding.
Two legitimate ways past it: have someone with the permissions create the binding, or hold the escalate verb on clusterroles, which should be rare and deliberate.
clusterroles.rbac.authorization.k8s.io "admin" is forbidden: attempt to grant extra privileges
Before reaching for cluster-admin
kubectl auth can-i --list on your own identity shows what you actually have, and comparing that against what the workload needs usually produces a small, specific Role. Binding cluster-admin to a ServiceAccount because the error was confusing is how clusters end up with a dozen unbounded identities that nobody dares remove.
A checklist
Frequently Asked Questions
Why can I get a Pod but not list Pods?
Because get and list are separate RBAC verbs. kubectl get pod my-pod issues a get against a named resource; kubectl get pods issues a list against the collection. A Role granting only get therefore allows fetching a Pod whose name you already know while refusing to enumerate them. Almost every read-only Role wants all three of get, list and watch, the last because controllers and kubectl get -w open watches, and a missing watch produces failures that only appear under those specific commands.
What is the difference between a Role and a ClusterRole?
A Role is namespaced and can only contain rules for namespaced resources; a ClusterRole is cluster-scoped and can also cover cluster-scoped resources such as nodes and persistent volumes. The part people miss is that the binding decides the scope of the grant, not the role. A ClusterRole referenced from a RoleBinding grants its rules only inside that binding's namespace, which is the standard way to define a permission set once and hand it out per namespace.
Why does my ServiceAccount have no permissions even though I created a RoleBinding?
Most often the RoleBinding is in the wrong namespace. A RoleBinding grants access only within the namespace it lives in, so a binding in default does nothing for requests against production, even when the ServiceAccount itself lives in default. The binding must be created in the target namespace, with the subject naming the account's own namespace. The second common cause is that the Pod is not using that ServiceAccount at all, because serviceAccountName was never set and it silently fell back to default.
What does "attempt to grant extra privileges" mean?
Kubernetes prevents privilege escalation: you cannot create a binding that grants permissions you do not hold yourself. Without it, anyone able to create RoleBindings in their own namespace could bind themselves cluster-admin. The legitimate ways round it are to have someone who holds the permissions create the binding, or to hold the escalate verb on clusterroles, which should be granted rarely and deliberately. Seeing this error usually means the permission set you are trying to hand out is wider than your own.
How do I find out exactly what permissions a workload needs?
Start from the errors rather than guessing. Run it with a minimal Role, collect each Forbidden message, and add precisely the verb, resource and apiGroup it names. kubectl auth can-i --list --as system:serviceaccount:<ns>:<name> shows the effective set after each change. It is slower than binding cluster-admin and it produces a Role you can actually justify later, which matters because over-broad ServiceAccount bindings are among the most common findings in a cluster security review.
Reference and practice
Learn the underlying concept
Other Kubernetes errors
Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.
Get notified about new tutorials and features.
• Roadmaps
• Interview Prep
• Certifications
• Company
© 2026 DevOpsLesson. All rights reserved.
Need to practice this scenario?
Simulate this error condition and practice recovery commands safely in our in-browser terminal.