Linkerd provides a lightweight service mesh for Kubernetes that adds workload identity, mutual TLS, traffic visibility, reliability features, and authorization without requiring changes to application code.
In this guide, we’ll install Linkerd, add an application namespace to the mesh, verify mTLS, install the observability stack, configure traffic management using the Kubernetes Gateway API, and restrict service-to-service communication using Linkerd authorization policies.
By the end, the architecture will look roughly like this:
The diagram could not be displayed. Its source is available below.
Diagram source
flowchart LR
subgraph Kubernetes
subgraph PodA["Frontend Pod"]
A[Frontend] --> PA[Linkerd Proxy]
end
subgraph PodB["API Pod"]
PB[Linkerd Proxy] --> B[API]
end
PA <-->|mTLS| PB
CP[Linkerd Control Plane]
CP -.-> PA
CP -.-> PB
endThe Linkerd proxies transparently intercept application traffic. When both workloads are part of the mesh, Linkerd automatically establishes mutually authenticated TLS connections between them.
Prerequisites
You need:
- A running Kubernetes cluster
kubectlconfigured for the cluster- Permissions to install cluster-scoped resources
- A Kubernetes version supported by the Linkerd release you intend to install
- A compatible Kubernetes Gateway API version
Verify Kubernetes access first:
kubectl cluster-info
kubectl get nodesAll nodes should be Ready.
For the examples below, we’ll use a namespace called production:
kubectl create namespace productionIf the namespace already exists, continue with the existing namespace.
Install the Linkerd CLI
The Linkerd CLI is used to install, validate, inspect, and troubleshoot the mesh.
On macOS:
brew install linkerdOn Linux, install the CLI using the installation method provided for the Linkerd distribution and version you intend to operate.
Verify the installation:
linkerd versionBefore the control plane is installed, it is normal for the server version to be unavailable.
Install the Kubernetes Gateway API
Modern Linkerd releases use Kubernetes Gateway API resources for features including:
- HTTP and gRPC routing
- retries
- timeouts
- route-level authorization
First determine whether Gateway API is already installed:
kubectl get crd httproutes.gateway.networking.k8s.ioYou can inspect the installed bundle version with:
kubectl get crds/httproutes.gateway.networking.k8s.io \
-o "jsonpath={.metadata.annotations.gateway\.networking\.k8s\.io/bundle-version}"If Gateway API is not installed, install a version supported by your Linkerd release.
For example:
kubectl apply -f \
https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.1/standard-install.yamlDo not blindly upgrade Gateway API on an existing cluster. Other controllers may use the same CRDs, and Linkerd versions support specific Gateway API version ranges.
Run the Linkerd Pre-Installation Check
Before installing the control plane, validate the cluster:
linkerd check --preResolve reported failures before continuing.
This verifies that the cluster and your current permissions satisfy Linkerd’s installation prerequisites.
Install the Linkerd Control Plane
Install the Linkerd CRDs first:
linkerd install --crds | kubectl apply -f -Then install the control plane:
linkerd install | kubectl apply -f -Watch the control-plane pods:
kubectl get pods -n linkerdRun Linkerd’s full validation:
linkerd checkYou can also inspect the deployments directly:
kubectl get deploy -n linkerdFor production, consider managing Linkerd with Helm or your GitOps workflow rather than relying on manually piped CLI manifests. The important point is to keep the installation configuration reproducible and version controlled.
Add an Application to the Mesh
Installing Linkerd does not automatically modify application workloads.
The Linkerd proxy must be added to pods that should participate in the mesh.
For namespace-based deployments, automatic injection is usually the simplest approach.
Annotate the namespace:
kubectl annotate namespace production linkerd.io/inject=enabledVerify it:
kubectl get namespace production -o yamlYou should see:
metadata:
annotations:
linkerd.io/inject: enabledInjection occurs when a pod is created, so restart existing deployments:
kubectl rollout restart deployment -n productionWatch the rollout:
kubectl get pods -n production -wVerify Proxy Injection
Inspect the application pods:
kubectl get pods -n productionA pod that previously showed:
READY 1/1will normally show:
READY 2/2after injection.
Inspect the container names:
kubectl get pod <pod-name> -n production -o jsonpath='{.spec.containers[*].name}'You should see the application container and:
linkerd-proxyValidate all meshed workloads in the namespace:
linkerd check --proxy -n productionHow Linkerd mTLS Works
One of Linkerd’s main advantages is that applications do not need to manage service-to-service TLS themselves.
For meshed traffic, Linkerd:
- Assigns a workload identity to each proxy.
- Issues short-lived certificates to proxies.
- Authenticates the destination.
- Encrypts proxy-to-proxy traffic with mTLS.
- Rotates workload certificates automatically.
The application can continue to use an ordinary Kubernetes Service URL:
http://api.production.svc.cluster.localThe actual pod-to-pod flow becomes:
The diagram could not be displayed. Its source is available below.
Diagram source
sequenceDiagram
participant A as Frontend
participant PA as Linkerd Proxy A
participant PB as Linkerd Proxy B
participant B as API
A->>PA: HTTP
PA->>PB: mTLS
PB->>B: HTTP
B->>PB: HTTP Response
PB->>PA: mTLS
PA->>A: HTTP ResponseThis gives applications authenticated and encrypted service-to-service communication without embedding certificate-management logic into each application.
Install Linkerd Viz
Install the Viz extension:
linkerd viz install | kubectl apply -f -Validate it:
linkerd viz checkYou can also run:
linkerd checkto include checks for installed extensions.
View Application Traffic
Inspect the meshed deployments:
linkerd viz stat deploy -n productionTypical metrics include:
- request rate
- success rate
- P50 latency
- P95 latency
- P99 latency
For example:
NAME MESHED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99
frontend 1/1 99.98% 12.4 4ms 11ms 18ms
api 3/3 99.99% 35.8 3ms 8ms 14msThese service-level metrics are available from the mesh without requiring each application to implement equivalent HTTP telemetry itself.
Open the Linkerd Dashboard
Start the dashboard:
linkerd viz dashboard &The dashboard lets you inspect:
- namespaces
- deployments
- pods
- service relationships
- success rates
- request rates
- latency
For production environments, do not expose the Viz dashboard publicly without appropriate authentication and access controls.
Verify mTLS
Inspect service-to-service relationships:
linkerd viz edges deployment -n productionYou can also inspect a pod’s Linkerd identity:
linkerd identity -n production <pod-name>An important distinction is that automatic mTLS authenticates traffic between meshed workloads, but encryption alone does not define which authenticated workload is allowed to call another workload.
That is the job of authorization policy.
From mTLS to Service Authorization
Assume our application contains:
frontend -> apiBoth workloads run in production, and the frontend uses the Kubernetes ServiceAccount:
frontendThe API should accept requests from the frontend but reject arbitrary workloads.
Linkerd authorization builds on the identities established by mTLS:
The diagram could not be displayed. Its source is available below.
Diagram source
flowchart LR
F["frontend\nServiceAccount: frontend"]
-->|"mTLS identity"| FP[Linkerd Proxy]
-->|"Authorized"| AP[API Linkerd Proxy]
--> API[API]
X["other workload"]
--> XP[Linkerd Proxy]
-.->|"Denied"| APWe will configure this in three steps:
- Define which API port is protected with a
Server. - Define which mTLS identity is trusted with
MeshTLSAuthentication. - Attach that authentication requirement with an
AuthorizationPolicy.
Define the Protected API Server
Assume the API pods have:
labels:
app: apiand the application listens on the named container port:
ports:
- name: http
containerPort: 8080Create a Linkerd Server:
apiVersion: policy.linkerd.io/v1beta3
kind: Server
metadata:
name: api
namespace: production
spec:
podSelector:
matchLabels:
app: api
port: http
proxyProtocol: HTTP/1Apply it:
kubectl apply -f api-server.yamlCheck it:
kubectl get server -n productionA critical behavior to understand is that a Server is a policy boundary. Once a Server selects a workload port, traffic must be authorized according to the applicable Linkerd policy.
Do not introduce a Server into production without also understanding which callers and Kubernetes probes need access.
Identify the Frontend mTLS Identity
Linkerd workload identities are derived from Kubernetes ServiceAccounts.
For the frontend ServiceAccount in the production namespace, the identity normally has the form:
frontend.production.serviceaccount.identity.linkerd.cluster.localConfirm that the frontend deployment actually uses that ServiceAccount:
kubectl get deploy frontend -n production \
-o jsonpath='{.spec.template.spec.serviceAccountName}'If it uses the namespace’s default ServiceAccount, create and assign a dedicated one before building identity-based authorization:
kubectl create serviceaccount frontend -n productionThen configure the deployment:
spec:
template:
spec:
serviceAccountName: frontendRestart it if necessary:
kubectl rollout restart deploy/frontend -n productionUsing dedicated ServiceAccounts is important because they become security identities in the mesh.
Define the Allowed mTLS Identity
Create a MeshTLSAuthentication resource:
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: frontend
namespace: production
spec:
identities:
- "frontend.production.serviceaccount.identity.linkerd.cluster.local"Apply it:
kubectl apply -f frontend-authentication.yamlThis resource describes an authenticated caller. It does not yet grant that caller access to anything.
Authorize the Frontend to Call the API
Create an AuthorizationPolicy targeting the API Server:
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: frontend-to-api
namespace: production
spec:
targetRef:
group: policy.linkerd.io
kind: Server
name: api
requiredAuthenticationRefs:
- group: policy.linkerd.io
kind: MeshTLSAuthentication
name: frontendApply it:
kubectl apply -f frontend-to-api.yamlThe effective model is now:
frontend ServiceAccount
|
| Linkerd mTLS identity
v
MeshTLSAuthentication
|
v
AuthorizationPolicy
|
v
Server: api
|
v
API workloadTraffic matching the policy is accepted. Traffic to that protected Server without an applicable authorization is denied.
Verify Linkerd Authorization
Inspect the authorization configuration:
kubectl get server,meshtlsauthentication,authorizationpolicy \
-n productionWith Viz installed, inspect effective authorization for the API:
linkerd viz authz -n production deploy/apiThis is one of the most useful commands when debugging Linkerd policy because it shows the server, route, authorization, unauthorized request rate, and traffic metrics together.
Test from the frontend:
kubectl exec -n production deploy/frontend -c <application-container> -- \
curl -sS http://apiThen test from another meshed workload using a different ServiceAccount.
The second request should not be accepted by the protected API unless that identity has also been authorized.
Do Not Forget Kubernetes Health Probes
Authorization can affect readiness and liveness probes.
Kubelet health checks do not behave like ordinary meshed application-to-application requests. If you introduce restrictive Server or route policies and the pod suddenly becomes unready, inspect the probe configuration before assuming the application is broken.
Check:
kubectl describe pod <api-pod> -n productionand:
linkerd viz authz -n production deploy/apiFor more granular per-route policies, explicitly account for the application’s health-check route and authorize the required probe traffic according to your cluster’s security model.
Avoid solving probe failures by broadly allowing all application traffic.
Configure Route-Level Authorization
Protecting the complete API port is useful, but HTTP-aware workloads can go further.
Suppose:
GET /api/v1/*should be callable by the frontend, while administrative routes require different policy.
Create an inbound HTTPRoute attached to the Linkerd Server:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-read
namespace: production
spec:
parentRefs:
- group: policy.linkerd.io
kind: Server
name: api
rules:
- matches:
- method: GET
path:
type: PathPrefix
value: /api/v1/You can then target that route with an AuthorizationPolicy instead of authorizing the entire Server.
For example:
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: frontend-api-read
namespace: production
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: api-read
requiredAuthenticationRefs:
- group: policy.linkerd.io
kind: MeshTLSAuthentication
name: frontendThis gives you HTTP-aware authorization while still basing trust on the caller’s cryptographic mesh identity.
For a first rollout, service-level policy is easier to operate. Introduce route-level authorization only where the additional granularity is justified.
Configure HTTP Routing with Gateway API
Suppose the API has two implementations:
api-stable
api-canaryBoth are Kubernetes Services:
kubectl get svc -n productionFor example:
api
api-stable
api-canaryAn outbound HTTPRoute attached to the api Service can control where client requests are sent.
Header-Based Canary Routing
The following rule sends requests containing:
x-canary: trueto the canary service:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-routing
namespace: production
spec:
parentRefs:
- name: api
kind: Service
group: ""
port: 80
rules:
- matches:
- headers:
- name: x-canary
value: "true"
backendRefs:
- name: api-canary
port: 80
- backendRefs:
- name: api-stable
port: 80Apply it:
kubectl apply -f api-routing.yamlNormal requests go to api-stable, while:
curl -H "x-canary: true" http://api.productionis routed to api-canary.
Configure Weighted Canary Traffic
Gateway API can also distribute traffic between multiple backends by weight:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-canary
namespace: production
spec:
parentRefs:
- name: api
kind: Service
group: ""
port: 80
rules:
- backendRefs:
- name: api-stable
port: 80
weight: 90
- name: api-canary
port: 80
weight: 10Apply it:
kubectl apply -f api-canary.yamlA rollout can then progress through weights such as:
99 / 1
90 / 10
75 / 25
50 / 50
0 / 100without modifying the application.
Configure Request Timeouts
A mesh can enforce client-side request timeouts so that calls do not wait indefinitely for an unhealthy backend.
For example:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api
namespace: production
spec:
parentRefs:
- name: api
kind: Service
group: ""
port: 80
rules:
- matches:
- path:
type: PathPrefix
value: /api
timeouts:
request: 10s
backendRequest: 3s
backendRefs:
- name: api
port: 80request limits the total request time, while backendRequest limits an individual backend attempt.
Because these are outbound policies, the calling workload must be meshed for Linkerd to enforce them.
Configure Retries
Retries can improve reliability for transient failures, but they must be bounded.
A route can carry Linkerd retry configuration, for example:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api
namespace: production
annotations:
retry.linkerd.io/http: "502-504"
retry.linkerd.io/limit: "2"
retry.linkerd.io/timeout: "2s"
spec:
parentRefs:
- name: api
kind: Service
group: ""
port: 80
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api
port: 80Be conservative with retries.
A failed request can otherwise become an amplification mechanism during an outage:
1 original request
+ N retry requestsPrefer retries for operations that are safe to repeat and combine them with explicit timeouts.
Observe Route-Level Behavior
Inspect outbound route metrics:
linkerd viz stat-outbound deploy/frontend -n productionRoute-level metrics are useful when investigating:
- a slow endpoint
- a failing route
- retry behavior
- timeout behavior
- canary traffic
ServiceProfiles vs Gateway API
Older Linkerd configurations often use ServiceProfile resources for route definitions, retries, timeouts, and route metrics.
They remain relevant to existing environments, but new configuration should generally use the standard Gateway API resources where supported.
Check for existing ServiceProfiles:
kubectl get serviceprofile -AThis matters during migration because an existing ServiceProfile can take precedence over an outbound HTTPRoute targeting the same Service.
If a new HTTPRoute appears to be ignored, check for a ServiceProfile early in the investigation.
Inspect Live Traffic
View deployment statistics:
linkerd viz stat deploy -n productionInspect routes:
linkerd viz routes deploy/api -n productionInspect service relationships:
linkerd viz edges deployment -n productionInspect live traffic:
linkerd viz tap deploy/api -n productiontap is especially useful when troubleshooting because it lets you observe requests without enabling verbose application logging.
Use it carefully in production because request metadata may contain sensitive information.
Production Installation Considerations
For production, treat Linkerd as a platform component.
Version-control at least:
- Linkerd version
- Gateway API version
- installation values
- trust-anchor management
- identity issuer configuration
- HA configuration
- proxy resource settings
- CNI configuration if used
- observability integration
Serverresources- authentication resources
- authorization policies
- routing and reliability policies
- upgrade procedure
Use dedicated Kubernetes ServiceAccounts for workloads whose identities need to be distinguished by authorization policy.
A service mesh is much easier to secure when application identity is intentional rather than every workload using the default ServiceAccount.
Troubleshooting Linkerd
Start with:
linkerd checkThis should normally be the first diagnostic command.
Check Proxy Injection
linkerd check --proxy -n productionThen inspect the pod:
kubectl describe pod <pod> -n productionVerify that linkerd-proxy exists.
Check Proxy Logs
kubectl logs deploy/api \
-n production \
-c linkerd-proxyCheck Application Metrics
linkerd viz stat deploy -n productionLook for:
- reduced success rate
- increased latency
- unexpected request volume
Inspect Live Requests
linkerd viz tap deploy/api -n productionInspect Traffic Relationships
linkerd viz edges deployment -n productionThis helps determine whether both ends of a connection are actually meshed.
Check Authorization
linkerd viz authz -n production deploy/apiThen inspect the policy resources:
kubectl get server,authorizationpolicy,meshtlsauthentication \
-n productionIf traffic stopped immediately after adding a Server, authorization policy is one of the first things to inspect.
Also check readiness and liveness probes.
Check Gateway API Resources
kubectl get httproute -AThen:
kubectl describe httproute <route-name> -n productionPay attention to resource status conditions.
Also verify that the installed Gateway API version is supported by your Linkerd version.
A Practical Rollout Strategy
Avoid enabling the mesh and restrictive policy across every namespace at once.
A safer progression is:
The diagram could not be displayed. Its source is available below.
Diagram source
flowchart LR
A[Install Control Plane]
--> B[Mesh Test Namespace]
--> C[Validate Traffic]
--> D[Validate mTLS]
--> E[Enable Observability]
--> F[Assign Dedicated ServiceAccounts]
--> G[Add Authorization]
--> H[Add Routing Policies]
--> I[Production Rollout]Start with a non-critical namespace:
kubectl annotate namespace test \
linkerd.io/inject=enabledVerify:
linkerd check --proxy -n testObserve normal traffic before introducing restrictive authorization.
Then add Server and authorization policies for one service at a time.
Final Verification
Run:
linkerd checkValidate application proxies:
linkerd check --proxy -n productionCheck traffic:
linkerd viz stat deploy -n productionCheck mTLS relationships:
linkerd viz edges deployment -n productionCheck authorization:
linkerd viz authz -n production deploy/apiInspect routing resources:
kubectl get httproute -n productionYou now have a Kubernetes service mesh providing:
- transparent proxying
- workload identity
- automatic mTLS
- service-to-service authorization
- service metrics
- request-level observability
- traffic routing
- canary deployments
- retries
- timeouts
The important security progression is:
Encrypt -> Authenticate -> AuthorizemTLS gives the mesh cryptographic workload identity. Authorization policy turns those identities into explicit rules about which services may communicate.
That is where a service mesh starts becoming a practical zero-trust control plane rather than simply an encrypted application network.



