Need HTTPS on Envoy Gateway? Use cert-manager + Let's Encrypt

Tested on Ubuntu 24.04.5 LTS
Package kubectl v1.36.5
Envoy Gateway v1.9.2
cert-manager v1.21.2
cmctl v2.6.1
Gateway API CRDs v1.6.1 (standard channel)
Applies to Any host with kubectl configured; any Kubernetes cluster that already runs Envoy Gateway and cert-manager
Lab environment Both controllers already installed, GatewayClass eg, and a public hostname whose A record reaches the Gateway
Privilege Normal user
Scope How cert-manager issues a Let's Encrypt certificate for an Envoy Gateway HTTPS listener, why HTTP-01 needs that hostname on port 80, how the listener Secret is filled, and how to read renewal time. Does not cover DNS-01, a private certificate authority, or migrating ingress-nginx.
Related guides Migrate ingress-nginx to Envoy Gateway
Migrate rewrite-target and regex paths
Kubernetes Gateway API
Kubernetes Secrets
Kubernetes Services
Short on time?Beta Get the gist of this 16-minute read in seconds, then ask follow-up questions

Envoy Gateway serves the certificate. cert-manager is the piece that obtains it from Let's Encrypt and writes it into the Secret the HTTPS listener already names.

Let's Encrypt checks that you control the hostname before it issues a certificate:

  • ACME is the protocol for that check
  • HTTP-01 is the validation method this page uses
  • cert-manager serves a one-time token at /.well-known/acme-challenge/...
  • Let's Encrypt requests that URL on public port 80

The examples on this page follow three rules:

  • The hostname is app.example.com
  • Start with the staging directory so a bad order does not spend production rate limit
  • Path rewrites are a separate change, covered in Migrate rewrite-target and regex paths
Client requests https://app.example.com
                   ↓
 Gateway HTTPS listener terminates TLS
                   ↓
 Secret edge-tls holds the certificate
                   ↓
    cert-manager filled that Secret
                   ↓
  HTTP-01 proved the name on port 80
                   ↓
   Service web receives the request

Those objects show up in order. The Gateway annotation and certificateRefs are the start. The Secret edge-tls is the end:

       Gateway annotation + certificateRefs
                        ↓
          cert-manager sees the Gateway
                        ↓
                   Certificate
                        ↓
                CertificateRequest
                        ↓
                    ACME Order
                        ↓
                    Challenge
                        ↓
   Temporary HTTPRoute, Service, and solver Pod
                        ↓
Let's Encrypt fetches the HTTP-01 token on port 80
                        ↓
                Challenge succeeds
                        ↓
   Secret edge-tls receives tls.crt and tls.key
                        ↓
      Envoy Gateway serves that certificate

cert-manager deletes that temporary HTTPRoute, Service, and solver Pod after the Challenge succeeds.


Check DNS and port 80 for Let's Encrypt HTTP-01

This page starts after the controllers are installed. The ingress-nginx to Envoy Gateway migration is the install walkthrough.

The cluster already had:

  • Envoy Gateway v1.9.2 with GatewayClass eg
  • cert-manager v1.21.2 with Gateway API support enabled
  • Gateway API CRDs v1.6.1 from the standard channel

cert-manager creates a Certificate from a Gateway only when that support is on:

  • The Helm value used here was config.gatewayAPI.enabled=true
  • Install the Gateway API CRDs before cert-manager starts
  • If those CRDs appear later, restart the cert-manager Deployment

Issuance and the finished HTTPS site are separate checks:

  • For issuance, DNS for app.example.com must reach this Gateway, and public TCP port 80 must reach its HTTP listener
  • For the site itself, public TCP port 443 must reach the HTTPS listener
  • Let's Encrypt performs HTTP-01 on port 80

Check every public A and AAAA record:

  • If the hostname publishes IPv6, that AAAA address must also reach this Gateway
  • A stale AAAA record can break validation even when the A record is correct. Let's Encrypt tries IPv6 first; some network-level IPv6 failures can fall back to IPv4, but an IPv6 server that responds incorrectly can cause the challenge to fail without that fallback.

Ask for the AAAA record first. This name had none, so the lookup printed nothing:

bash
dig +short AAAA app.example.com

dig prints nothing when the name has no AAAA record. That is the result you want unless you really do serve this Gateway on IPv6.

Then ask for the A record:

bash
dig +short A app.example.com

This printed one IPv4 address. That address has to be the Gateway. A second A record, or an AAAA record aimed at a different host, can send the challenge somewhere else.

Port 80 must remain reachable for HTTP-01:

  • cert-manager creates a temporary HTTPRoute for the exact /.well-known/acme-challenge/<token> path
  • That route should handle the challenge even when normal HTTP traffic redirects to HTTPS
  • Let's Encrypt follows HTTP-to-HTTPS redirects
  • If issuance fails after you add redirects, inspect the solver HTTPRoute and test the challenge URL
  • Incorrect routing, another proxy in front of the Gateway, or a broken IPv6 path can still send the challenge somewhere else

The account contact and the certificate name are different:

  • The email does not have to be an address at app.example.com. Let's Encrypt uses it for account notices
  • The name on the certificate comes from the Gateway listener hostname

DNS-01 would prove the name through a DNS provider API instead of port 80. That is a different solver and a different page. A cluster with no public name can use a local CA instead, covered in Envoy Gateway local HTTPS with a CA.


How cert-manager fills the Envoy Gateway certificate

The same Gateway is in two flows. Port 80 carries the ACME challenge. Port 443 carries normal traffic to the backend Service.

       HTTP port 80
            ↓
      ACME challenge
            ↓
temporary solver HTTPRoute
       HTTPS port 443
              ↓
     application traffic
              ↓
HTTPRoute edge to Service web

The issuer and the Gateway point at each other for different reasons:

  • The Gateway annotation cert-manager.io/cluster-issuer tells cert-manager which issuer should create the TLS certificate
  • The ClusterIssuer field gatewayHTTPRoute.parentRefs tells cert-manager which Gateway's port 80 listener should carry the challenge

This guide uses a ClusterIssuer:

  • It is cluster-scoped, so Gateways in more than one namespace can reference it
  • A normal Issuer is namespaced and can issue only for resources in its own namespace

Two Secrets show up in the YAML. They are not the same object:

Secret Contains Who uses it
letsencrypt-staging-account-key ACME account private key the ClusterIssuer, to sign in to Let's Encrypt
edge-tls site certificate and private key Envoy, to terminate HTTPS for clients

For this annotation flow, the Certificate and Secret stay with the Gateway:

  • cert-manager creates both in the Gateway's namespace
  • edge-tls appears in default, beside Gateway eg
  • You do not create that TLS Secret yourself

Create a staging Let's Encrypt ClusterIssuer

Use the staging directory until HTTP-01 succeeds:

  • The directory is acme-staging-v02.api.letsencrypt.org
  • The certificate is a real ACME result
  • Public clients do not trust it
  • A failed order against the production directory still counts toward Let's Encrypt rate limits

The fields that matter:

  • server selects staging or production
  • email is the ACME account contact
  • privateKeySecretRef stores the account key, not the site certificate
  • gatewayHTTPRoute.parentRefs selects Gateway eg in default as the port 80 parent for the challenge route
yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: you@example.com
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    solvers:
    - http01:
        gatewayHTTPRoute:
          parentRefs:
          - kind: Gateway
            name: eg
            namespace: default

This issuer is applied with the Gateway in the next section. cert-manager creates and manages the ACME account-key Secret referenced by privateKeySecretRef. Once the ClusterIssuer is Ready, the ACME account is registered and the issuer can process certificate requests.


Attach cert-manager to the Envoy Gateway HTTPS listener

A Gateway API HTTPS listener needs three settings. cert-manager copies the hostname into the Certificate dnsNames, and Envoy Gateway serves the one Secret named there:

  • hostname on the listener
  • tls.mode: Terminate
  • one certificateRefs entry

The HTTP listener is where the challenge attaches:

  • It has no hostname, so it can accept the challenge route for app.example.com
  • allowedRoutes is Same
  • The solver HTTPRoute is created in the Challenge namespace, which here is default, next to the Gateway
  • Use All only when solver routes are supposed to come from other namespaces
yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: eg
  namespace: default
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
spec:
  gatewayClassName: eg
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: Same
  - name: https
    protocol: HTTPS
    hostname: app.example.com
    port: 443
    allowedRoutes:
      namespaces:
        from: Same
    tls:
      mode: Terminate
      certificateRefs:
      - kind: Secret
        name: edge-tls

Normal application traffic is a separate HTTPRoute. It attaches to both listeners and sends app.example.com to Service web:

yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: edge
  namespace: default
spec:
  parentRefs:
  - name: eg
    sectionName: http
  - name: eg
    sectionName: https
  hostnames:
  - app.example.com
  rules:
  - backendRefs:
    - name: web
      port: 80

Save the issuer, the Gateway, and HTTPRoute edge with GatewayClass eg and the web backend, which were already installed.

The HTTP listener in that file:

  • It is set to Same
  • The solver route in this test was created in default, so Same is enough
  • An earlier copy used All on that listener, which also accepted the route
yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: eg
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web
        image: nginx:1.27-alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: default
spec:
  selector:
    app: web
  ports:
  - name: http
    port: 80
    targetPort: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: eg
  namespace: default
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
spec:
  gatewayClassName: eg
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: Same
  - name: https
    protocol: HTTPS
    hostname: app.example.com
    port: 443
    allowedRoutes:
      namespaces:
        from: Same
    tls:
      mode: Terminate
      certificateRefs:
      - kind: Secret
        name: edge-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: edge
  namespace: default
spec:
  parentRefs:
  - name: eg
    sectionName: http
  - name: eg
    sectionName: https
  hostnames:
  - app.example.com
  rules:
  - backendRefs:
    - name: web
      port: 80
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: you@example.com
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    solvers:
    - http01:
        gatewayHTTPRoute:
          parentRefs:
          - kind: Gateway
            name: eg
            namespace: default

Apply the file. On this cluster the objects already existed, so Kubernetes reported configured and unchanged. A first apply of the same file prints created:

bash
kubectl apply -f edge-tls.yaml
output
gatewayclass.gateway.networking.k8s.io/eg unchanged
deployment.apps/web unchanged
service/web unchanged
gateway.gateway.networking.k8s.io/eg configured
httproute.gateway.networking.k8s.io/edge configured
clusterissuer.cert-manager.io/letsencrypt-staging configured

Until the Challenge finishes, Secret edge-tls does not exist and the HTTPS listener has nothing to serve.


Follow the Let's Encrypt HTTP-01 challenge

cert-manager creates Certificate edge-tls from the annotation and the Secret reference. It stays not Ready while the ACME order is waiting.

List the Certificate, the Order, and the Challenge together:

bash
kubectl get certificate,order,challenge -A
output
NAMESPACE   NAME                                   READY   SECRET     AGE
default     certificate.cert-manager.io/edge-tls   False   edge-tls   26s

NAMESPACE   NAME                                               STATE     AGE
default     order.acme.cert-manager.io/edge-tls-1-1246097667   pending   23s

NAMESPACE   NAME                                                              STATE     DOMAIN            AGE
default     challenge.acme.cert-manager.io/edge-tls-1-1246097667-1385337141   pending   app.example.com   11s

READY is False and the Challenge is pending for the first half minute. Describe the Challenge if it stays there. On this run the reason was HTTP 503:

bash
kubectl describe challenge -n default edge-tls-1-1246097667-1385337141
output
Status:
  Presented:     true
  Presented At:  2026-10-03T17:25:59Z
  Processing:    true
  Reason:        Waiting for HTTP-01 challenge propagation: wrong status code '503', expected '200'
  State:         pending

On this run the 503 was temporary:

  • The challenge route existed before its solver backend was ready
  • The same Challenge succeeded a few seconds later
  • A 503 means the Gateway could not hand the request to a usable backend
  • If it persists, inspect the temporary solver Service, Pod, endpoints, and HTTPRoute

While the Challenge is pending, a route named cm-acme-http-solver- should be attached to Gateway eg. List HTTPRoutes and look for that prefix:

bash
kubectl get httproute -A
output
NAMESPACE   NAME                        HOSTNAMES              AGE
default     cm-acme-http-solver-s7xgh   ["app.example.com"]    16s
default     edge                        ["app.example.com"]    33s

The solver objects share one prefix:

  • The Service and Pod names start with cm-acme-http-solver
  • If the route is missing, or kubectl describe httproute shows it was not accepted, the Challenge cannot finish
  • After success, cert-manager deletes the route, Service, and Pod

The solver Pod then started, Let's Encrypt fetched the token, and the Challenge completed. These are the later events:

bash
kubectl get events -n default
output
31s   Normal   Started            challenge/edge-tls-1-1246097667-1385337141   Challenge scheduled for processing
29s   Normal   Presented          challenge/edge-tls-1-1246097667-1385337141   Presented challenge using HTTP-01 challenge mechanism
9s    Normal   DomainVerified     challenge/edge-tls-1-1246097667-1385337141   Domain "app.example.com" verified with "HTTP-01" validation
5s    Normal   CertificateIssued  certificaterequest/edge-tls-1                Certificate fetched from issuer successfully
5s    Normal   Issuing            certificate/edge-tls                          The certificate has been successfully issued

DomainVerified means Let's Encrypt accepted the token. The Certificate then becomes Ready:

bash
kubectl get certificate edge-tls
output
NAME       READY   SECRET     AGE
edge-tls   True    edge-tls   53s

Verify the staging Let's Encrypt certificate

Read the issuer and the dates from Secret edge-tls:

  • A staging success still says STAGING in the issuer name
  • Clients that trust only public roots reject this certificate
  • Keep this issuer until that check looks right, then move to production
bash
kubectl get secret edge-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -issuer -subject -dates
output
issuer=C = US, O = Let's Encrypt, CN = (STAGING) Ersatz Emmer YR2
subject=CN = app.example.com
notBefore=Oct  3 16:27:49 2026 GMT
notAfter=Jan  1 16:27:48 2027 GMT

Read these fields before you switch directories:

  • notAfter is 90 days after notBefore
  • The issuer name changes when you leave the staging directory
  • Clients match the hostname against the Subject Alternative Name, not the Common Name
  • The production read below prints that extension with -ext subjectAltName

Switch Envoy Gateway to Let's Encrypt production

Production changes three things and leaves the staging issuer in place, so a test hostname can point back at staging later:

  • The same issuer fields
  • The production ACME directory
  • A different account-key Secret, letsencrypt-account-key
yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: you@example.com
    privateKeySecretRef:
      name: letsencrypt-account-key
    solvers:
    - http01:
        gatewayHTTPRoute:
          parentRefs:
          - kind: Gateway
            name: eg
            namespace: default

Apply that issuer. Ready means the ACME account was registered:

bash
kubectl apply -f letsencrypt.yaml
output
clusterissuer.cert-manager.io/letsencrypt created

Wait for Ready before you treat the annotation change as the new signer:

bash
kubectl wait --for=condition=Ready clusterissuer/letsencrypt --timeout=120s
output
clusterissuer.cert-manager.io/letsencrypt condition met

Point the Gateway at the production issuer. cert-manager updates the Certificate issuerRef to match:

bash
kubectl annotate gateway eg cert-manager.io/cluster-issuer=letsencrypt --overwrite
output
gateway.gateway.networking.k8s.io/eg annotated

The Certificate goes Ready=False while the production order runs, then returns to True with a new notBefore:

bash
kubectl get certificate edge-tls -o jsonpath='issuer={.spec.issuerRef.name} ready={.status.conditions[?(@.type=="Ready")].status} notBefore={.status.notBefore} renewal={.status.renewalTime} notAfter={.status.notAfter}{"\n"}'
output
issuer=letsencrypt ready=True notBefore=2026-10-03T16:29:53Z renewal=2026-12-02T16:29:52Z notAfter=2027-01-01T16:29:52Z

issuer=letsencrypt is the production ClusterIssuer. Read the Secret again, including the SAN:

bash
kubectl get secret edge-tls -o jsonpath='{.data.tls\.crt}' \
  | base64 -d | openssl x509 -noout -issuer -subject -dates -ext subjectAltName
output
issuer=C = US, O = Let's Encrypt, CN = YR1
subject=CN = app.example.com
notBefore=Oct  3 16:29:53 2026 GMT
notAfter=Jan  1 16:29:52 2027 GMT
X509v3 Subject Alternative Name: 
    DNS:app.example.com

Check the name clients actually verify:

  • Look for DNS:app.example.com under X509v3 Subject Alternative Name
  • That SAN is what modern TLS clients use for hostname verification
  • A Common Name may also be present
  • Do not use the CN as the hostname check

Confirm you left staging:

  • The issuer line no longer contains STAGING
  • YR1 was the production intermediate in this test
  • Intermediate names change, so do not require a particular CN
  • The real check is a missing STAGING string plus a trusted handshake

Call the hostname directly. That checks the name on the certificate, not only that some certificate was presented:

bash
curl -sS -o /dev/null -w 'http=%{http_code} verify=%{ssl_verify_result}\n' https://app.example.com/
output
http=200 verify=0

http=200 and verify=0 together mean the request worked:

  • ssl_verify_result=0 is the OpenSSL success code curl uses for chain verification
  • Calling the hostname also checks that the certificate name matches
  • HTTP 200 means the request reached the application

If verify is not 0:

  • the Secret may still hold the staging certificate
  • or the client is not talking to this Gateway

Read the issuer line before you request another production certificate.


Renew the Let's Encrypt certificate with cmctl

With no custom renewal setting, cert-manager picks the time from the certificate lifetime:

  • Renewal is scheduled about two-thirds of the way through that lifetime
  • This roughly 90-day certificate leaves about 30 days before expiry
  • That is why renewalTime is 2 December, not the January notAfter
  • renewBefore or renewBeforePercentage changes the schedule

These fields are from the first staging certificate, before the production switch:

bash
kubectl get certificate edge-tls -o jsonpath='notBefore={.status.notBefore} renewal={.status.renewalTime}{"\n"}'
output
notBefore=2026-10-03T16:27:49Z renewal=2026-12-02T16:27:48Z

cmctl renew queues a new issuance and returns before the certificate is finished:

  • cert-manager creates a new CertificateRequest and runs the normal issuer flow
  • This run used the staging certificate, before the Gateway annotation moved to production
  • The same command works on the production Certificate
bash
cmctl renew edge-tls -n default
output
Manually triggered issuance of Certificate default/edge-tls

The command returns as soon as the request is queued. Read the Certificate again after the new order finishes. notBefore moved from 16:27:49Z to 16:28:58Z, and renewalTime moved with it:

bash
kubectl get certificate edge-tls -o jsonpath='notBefore={.status.notBefore} renewal={.status.renewalTime}{"\n"}'
output
notBefore=2026-10-03T16:28:58Z renewal=2026-12-02T16:28:57Z

edge-tls-2 is that new request, still signed by letsencrypt-staging:

bash
kubectl get certificaterequest -n default
output
NAME         APPROVED   DENIED   READY   ISSUER                REQUESTER                                         AGE
edge-tls-2   True                True    letsencrypt-staging   system:serviceaccount:cert-manager:cert-manager   5s

Port 80 has to stay open for the next HTTP-01 challenge, whether it is the December renewal or another cmctl renew.


Troubleshoot Let's Encrypt HTTP-01 failures

Symptom Likely cause Fix
Challenge stays pending with HTTP 503 The solver backend had no ready endpoints, or port 80 does not reach this Gateway Inspect the cm-acme-http-solver HTTPRoute, Service, Pod, and endpoints. Confirm A and AAAA both reach this Gateway
Challenge never becomes DomainVerified DNS, IPv6, or routing sends the token somewhere else dig the A and AAAA records. Open the solver HTTPRoute and request the challenge URL yourself
READY stays False and no Challenge appears Gateway API support is off, the listener has no hostname, or the annotation names a missing issuer Set config.gatewayAPI.enabled=true, set hostname on the HTTPS listener, and confirm the ClusterIssuer is Ready
openssl issuer contains STAGING The Gateway annotation still selects letsencrypt-staging Annotate cert-manager.io/cluster-issuer=letsencrypt and wait for a new notBefore
curl only succeeds with -k The presented certificate is still the staging one Confirm the issuer does not contain STAGING, then check the live certificate with curl or openssl. Do not require a specific intermediate CN
Renewal does not happen on the expiry date Renewal is about two-thirds of the way through the lifetime Read status.renewalTime. Use cmctl renew when you need a new certificate now

Summary

cert-manager talks to Let's Encrypt. The Gateway HTTPS listener only names the hostname and Secret edge-tls. HTTP-01 proves that hostname on port 80 through a temporary solver route, and cert-manager writes tls.crt and tls.key into the Secret. Envoy serves that certificate on port 443.

Start with the staging directory. An issuer name that contains STAGING means the solver path works and that clients should not trust it yet. Switching the Gateway annotation to the production issuer causes cert-manager to issue a production certificate and update the certificate data in the same edge-tls Secret. Trust the SAN DNS:app.example.com and a curl result of verify=0, not a particular intermediate name.

Renewal is already scheduled when the Certificate is Ready. With the default settings it lands about two-thirds of the way through a 90-day certificate, which is why this one shows 2 December. cmctl renew queues that same issuance immediately. Port 80 stays part of the setup, because the next challenge uses it too.


References


Frequently Asked Questions

Does the contact email have to match the domain?

No.

  • Let's Encrypt stores the email on the ACME account and uses it for expiry notices
  • The name on the certificate is the hostname on the Gateway HTTPS listener
  • Clients check that name in the Subject Alternative Name

Why does curl reject the staging certificate?

The staging certificate is not publicly trusted:

  • The issuer name contains STAGING
  • It is signed by the Let's Encrypt staging CA
  • Use it to prove HTTP-01, then point the Gateway annotation at the production issuer

Why did the challenge report HTTP 503?

On this run the 503 cleared by itself:

  • The challenge route existed before the solver Pod was ready
  • The same Challenge succeeded a few seconds later
  • If 503 persists, check the temporary HTTPRoute, Service, Pod, and endpoints

Do I have to keep port 80 open after HTTPS works?

Yes, for this HTTP-01 issuer.

  • Renewal presents another challenge on port 80
  • Closing port 80 leaves the current certificate in place until notAfter
  • The next renewal then fails

When does cert-manager renew the certificate?

With no custom renewal setting:

  • cert-manager schedules renewal about two-thirds of the way through the certificate lifetime
  • For a 90-day Let's Encrypt certificate, that is about 30 days before notAfter
  • That time is the status.renewalTime field
  • cmctl renew queues the issuance and returns before it finishes
Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)