Lesson 33 of 66 · 18 min read · Updated

HTTPS with Gateway API and cert-manager

In the previous article you looked at the Gateway API resource model: who owns the GatewayClass, who owns the Gateway, how listeners decide which routes may attach, and how to read the status conditions. Everything so far has been plain HTTP on port 80. No real application ships like that.

This article adds HTTPS. In Gateway API, TLS termination is not an annotation and not a controller-specific setting. It is a field on the listener, which makes it portable across implementations. You will first terminate TLS with a certificate you create by hand, so you can see every moving part. Then you will hand the job to cert-manager, which watches the Gateway and issues and renews certificates for you.

We will cover the following topics:

  1. How an HTTPS listener terminates TLS, and what tls.mode: Terminate actually means
  2. Where the certificate lives, and why the Secret must sit next to the Gateway
  3. Creating a local CA and a server certificate with openssl
  4. Installing cert-manager with Gateway API support switched on
  5. Letting cert-manager issue certificates from a single Gateway annotation
  6. The Let's Encrypt HTTP-01 solver shape for production
  7. Redirecting HTTP to HTTPS with the RequestRedirect filter
  8. Testing HTTPS against a local cluster with curl --resolve, --cacert and -k

Technical Requirements

  1. The minikube cluster from the previous articles, with Envoy Gateway v1.9.2 installed in envoy-gateway-system and the eg GatewayClass accepted.
  2. The storefront namespace with the kiosk-api:1.0 Deployment (3 replicas) and its ClusterIP Service kiosk-api (port 80 → 8080).
  3. kubectl, Helm 3 and openssl on your machine.
  4. curl. Every test in this article goes through kubectl port-forward, so you do not need a LoadBalancer or a public DNS name.

Core Concepts

What "Terminate" Means

A Gateway listener with protocol: HTTPS accepts TLS connections. The tls.mode field decides what happens to them.

ModeWhat the Gateway doesWho holds the certificate
TerminateDecrypts the TLS session, reads the HTTP request, routes it with HTTPRoute rules, and sends plain HTTP (or new TLS) to the backendThe Gateway, via certificateRefs
PassthroughForwards the encrypted bytes untouched, routing only on the SNI host name. Used with TLSRoute, not HTTPRouteThe backend application

Terminate is the default and the one you want for almost every web API. Because the Gateway decrypts the request, it can see the path, the headers and the method. That is what makes all of the HTTPRoute matching in the next article possible. With Passthrough the Gateway is blind to everything except the host name.

Terminating at the Gateway also means your .NET application keeps listening on plain HTTP on port 8080. You do not mount certificates into every Pod, and you do not configure Kestrel for TLS. The certificate lives in exactly one place.

The HTTPS Listener

An HTTPS listener has four parts that matter:

listeners:
- name: https
protocol: HTTPS
port: 443
hostname: storefront.local
tls:
mode: Terminate
certificateRefs:
- kind: Secret
group: ""
name: storefront-tls
  1. port: 443 becomes a port on the Envoy Service that Envoy Gateway creates for this Gateway.
  2. hostname restricts the listener to one host name. It is optional for a hand-made certificate, but cert-manager requires it, as you will see.
  3. certificateRefs points at a Secret of type kubernetes.io/tls, with the keys tls.crt and tls.key.
  4. kind: Secret and group: "" are the defaults. Writing them out makes the YAML easier to read.

Why the Secret Must Live Next to the Gateway

A certificateRefs entry can name a Secret in another namespace. But a Gateway reaching into another namespace is a security boundary crossing, so Gateway API only allows it when the owner of that namespace creates a ReferenceGrant that permits it. Without the grant, the listener reports ResolvedRefs: False with the reason RefNotPermitted, and it serves nothing.

The simplest rule is the one this series follows: the TLS Secret lives in the same namespace as the Gateway, here storefront. ReferenceGrant gets a full article later in the series.

Two Ways to Get a Certificate

ApproachGood forRenewal
openssl + kubectl create secret tlsLearning, air-gapped labs, a certificate issued by another teamYou, by hand, before it expires
cert-managerEverything elseAutomatic, about 30 days before expiry by default

You will do both. The manual version shows that there is no magic: a Gateway simply reads a TLS Secret. The cert-manager version shows how to stop thinking about it.

How cert-manager Uses a Gateway

cert-manager has a component called the gateway-shim. When Gateway API support is enabled, it watches Gateway resources. When it sees a Gateway with the annotation cert-manager.io/cluster-issuer (or cert-manager.io/issuer for a namespaced Issuer), it reads every listener and creates a Certificate resource for each one that qualifies. The Certificate is then issued into the Secret named in certificateRefs.

A listener qualifies only when all of these hold:

Listener fieldRequirement
hostnameMust not be empty. It becomes the DNS name on the certificate
tls.modeMust be Terminate. Passthrough is not supported
tls.certificateRefs[].nameMust not be empty. It is the Secret cert-manager writes
tls.certificateRefs[].kindIf set, must be Secret
tls.certificateRefs[].namespaceIf set, must be the Gateway's own namespace

Listeners that fail these checks are skipped silently. If cert-manager does not create a Certificate, check these five fields first.

Two details are worth remembering. First, since cert-manager 1.15 Gateway API support no longer needs a feature gate, but it is still off by default and must be enabled with the Helm value config.gatewayAPI.enabled=true. Second, the Gateway API CRDs must exist before cert-manager starts. If you install the CRDs afterwards, restart the cert-manager Deployment. In this series Envoy Gateway installed the CRDs in article two, so the order is already right.

Issuers for Local and Production Use

An Issuer or ClusterIssuer tells cert-manager where certificates come from. A ClusterIssuer works for every namespace, which suits a shared Gateway.

Issuer typeTrusted by browsersNeeds the cluster to be reachable from the internet
selfSignedNoNo
ca (your own CA)Only by machines that trust your CANo
acme with Let's EncryptYesYes, for HTTP-01

On minikube you cannot complete a Let's Encrypt challenge, because Let's Encrypt must reach http://<your-host>/.well-known/acme-challenge/... from the internet. So the hands-on part uses a small CA issuer built inside the cluster. The production shape is shown alongside it, so the only change later is the issuer name in one annotation.

The HTTP-01 Solver for Gateway API

For Ingress, cert-manager solves HTTP-01 challenges by creating a temporary Ingress. For Gateway API it creates a temporary HTTPRoute instead. You tell it which Gateway to attach that route to with gatewayHTTPRoute.parentRefs:

solvers:
- http01:
gatewayHTTPRoute:
parentRefs:
- name: storefront-gateway
namespace: storefront
kind: Gateway

The temporary route matches the exact path /.well-known/acme-challenge/<token> on port 80. cert-manager deletes it once the certificate is issued. cert-manager never edits your Gateway, so the Gateway must already have an HTTP listener on port 80. If it does not, the challenge has nowhere to attach.

Redirecting HTTP to HTTPS

Once HTTPS works, plain HTTP should do nothing except send the client to HTTPS. In Gateway API that is a route rule with a RequestRedirect filter and no backend:

rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301

The allowed status codes are 301, 302, 303, 307 and 308. Only 301 and 302 are Core and guaranteed on every implementation; the others are Extended. The default is 302. Use 301 for a permanent redirect.

Attach this route only to the http listener, using sectionName: http in its parentRefs. Attach the real application route only to the https listener. If you attach the application route to both, plain HTTP keeps working and the redirect becomes a coin toss decided by route precedence.

Does the redirect break the ACME challenge? No. The challenge route uses an Exact path match, and an exact path always wins over the redirect rule's implicit / prefix match. Precedence rules are covered in detail in the next article.

Hands-On: Kubernetes Commands

Find the Envoy Service for the Gateway

Envoy Gateway creates one Service per Gateway in envoy-gateway-system. These labels identify it.

export ENVOY_SERVICE=$(kubectl get svc -n envoy-gateway-system \
--selector=gateway.envoyproxy.io/owning-gateway-namespace=storefront,gateway.envoyproxy.io/owning-gateway-name=storefront-gateway \
-o jsonpath='{.items[0].metadata.name}')

Forward both HTTP and HTTPS ports

One port-forward can carry several ports. Port 443 only exists on the Service after you add the HTTPS listener.

kubectl -n envoy-gateway-system port-forward service/$ENVOY_SERVICE 8888:80 8443:443

Create a TLS Secret from files

The Secret type is kubernetes.io/tls, with the keys tls.crt and tls.key.

kubectl create secret tls storefront-tls -n storefront --cert=storefront.local.crt --key=storefront.local.key

Inspect a certificate inside a Secret

Decodes the certificate and prints its subject, issuer, SAN and expiry. This form works with both OpenSSL and the LibreSSL that ships with macOS, which does not know the newer -ext option.

kubectl get secret storefront-tls -n storefront -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -text | grep -E "Issuer:|Subject:|DNS:|Not After"

Check the listener status

Each listener reports its own conditions. ResolvedRefs tells you whether the certificate Secret was found and allowed.

kubectl get gateway storefront-gateway -n storefront -o jsonpath='{range .status.listeners[*]}{.name}{" "}{range .conditions[*]}{.type}={.status} {end}{"\n"}{end}'

Call HTTPS on a host name that does not resolve

--resolve pins storefront.local:8443 to 127.0.0.1, so curl sends the right SNI and Host without editing /etc/hosts.

curl --resolve storefront.local:8443:127.0.0.1 --cacert ca.crt https://storefront.local:8443/

Skip verification when you only care about routing

-k accepts any certificate. Useful for a quick check, never as proof that TLS is set up correctly.

curl -k --resolve storefront.local:8443:127.0.0.1 https://storefront.local:8443/

List cert-manager resources

Each step of issuance is a resource you can inspect.

kubectl get clusterissuer,certificate,certificaterequest,order,challenge -A

Explain why a Certificate is not ready

The Events and conditions at the bottom usually name the problem.

kubectl describe certificate storefront-cm-tls -n storefront

Step-by-Step Example

The Scenario

The storefront team wants kiosk-api to be reachable only over HTTPS at storefront.local. Plain HTTP requests must be redirected permanently. The platform team wants certificates to renew themselves. You will get there in two stages: first with a certificate made by hand, then with cert-manager.

All files are created in one working directory. The numbers in the file names give the order to apply them.

Step 1 — Start from a Clean Route Table

Earlier articles attached routes to the http listener. This article redefines the routing for storefront.local from scratch, so remove the old routes first. The application keeps running; only routes are deleted.

kubectl delete httproute --all -n storefront
kubectl get deploy,svc -n storefront
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/kiosk-api 3/3 3 3 2d

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kiosk-api ClusterIP 10.105.41.17 <none> 80/TCP 2d

Step 2 — Create a Local CA and a Server Certificate

A self-signed server certificate works, but curl cannot verify it without -k. A tiny CA is only two extra commands and lets you verify properly with --cacert.

Create the CA:

openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 \
-subj '/O=Storefront Dev/CN=Storefront Dev CA' \
-keyout ca.key -out ca.crt

Create a key and a signing request for storefront.local:

openssl req -new -nodes -newkey rsa:2048 \
-subj '/O=Storefront/CN=storefront.local' \
-keyout storefront.local.key -out storefront.local.csr

Sign it with the CA. Modern clients ignore the CN and check only the Subject Alternative Name, so the SAN is not optional.

openssl x509 -req -sha256 -days 365 \
-CA ca.crt -CAkey ca.key -set_serial 1 \
-in storefront.local.csr -out storefront.local.crt \
-extfile <(printf "subjectAltName=DNS:storefront.local")

Store the certificate in the Gateway's namespace:

kubectl create secret tls storefront-tls -n storefront \
--cert=storefront.local.crt --key=storefront.local.key

Step 3 — Add the HTTPS Listener

This is the full Gateway. The http listener stays, because it will carry the redirect. Note that hostname is now set on both listeners, so this Gateway only answers for storefront.local.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: storefront-gateway
namespace: storefront
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
hostname: storefront.local
allowedRoutes:
namespaces:
from: Same
- name: https
protocol: HTTPS
port: 443
hostname: storefront.local
tls:
mode: Terminate
certificateRefs:
- kind: Secret
group: ""
name: storefront-tls
allowedRoutes:
namespaces:
from: Same
kubectl apply -f 1-storefront-gateway.yaml
kubectl get gateway storefront-gateway -n storefront -o jsonpath='{range .status.listeners[*]}{.name}{" "}{range .conditions[*]}{.type}={.status} {end}{"\n"}{end}'
http Programmed=True Accepted=True ResolvedRefs=True
https Programmed=True Accepted=True ResolvedRefs=True

ResolvedRefs=True on https means the Secret was found and is a valid TLS Secret. Delete the Secret and this flips to False, which is the quickest way to diagnose a broken HTTPS listener.

Step 4 — Route the Application on HTTPS Only

sectionName: https attaches the route to one listener only.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: kiosk-api
namespace: storefront
spec:
parentRefs:
- name: storefront-gateway
sectionName: https
hostnames:
- storefront.local
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: kiosk-api
port: 80
kubectl apply -f 2-kiosk-api-route.yaml
kubectl get httproute kiosk-api -n storefront -o jsonpath='{range .status.parents[*].conditions[*]}{.type}={.status} ({.reason}){"\n"}{end}'
Accepted=True (Accepted)
ResolvedRefs=True (ResolvedRefs)

Step 5 — Test HTTPS with the CA

Find the Envoy Service and start the port-forward in a second terminal. The Service picked up port 443 when the listener was added.

export ENVOY_SERVICE=$(kubectl get svc -n envoy-gateway-system \
--selector=gateway.envoyproxy.io/owning-gateway-namespace=storefront,gateway.envoyproxy.io/owning-gateway-name=storefront-gateway \
-o jsonpath='{.items[0].metadata.name}')
kubectl get svc $ENVOY_SERVICE -n envoy-gateway-system
kubectl -n envoy-gateway-system port-forward service/$ENVOY_SERVICE 8888:80 8443:443

On Docker Desktop's built-in kind cluster the Envoy LoadBalancer Service is also published on localhost, so --resolve storefront.local:443:127.0.0.1 https://storefront.local/ works without the port-forward, as long as nothing else on your machine holds ports 80 and 443.

Back in the first terminal:

curl -v --resolve storefront.local:8443:127.0.0.1 --cacert ca.crt https://storefront.local:8443/
* Server certificate:
* subject: O=Storefront; CN=storefront.local
* issuer: O=Storefront Dev; CN=Storefront Dev CA
* subjectAltName: host "storefront.local" matched cert's "storefront.local"
* SSL certificate verify ok.
...
{"servedByPod":"kiosk-api-6d9c8f7b5-x2kqp","podIp":"10.244.0.31","node":"minikube","path":"/"}

Three things were proven at once: Envoy presented the certificate from the Secret, curl trusted it through your CA, and the decrypted request reached a kiosk-api Pod on plain HTTP.

Now try the same request without --cacert. curl uses the system trust store, which does not know your CA:

curl --resolve storefront.local:8443:127.0.0.1 https://storefront.local:8443/
curl: (60) SSL certificate problem: unable to get local issuer certificate

That failure is correct behaviour, not a broken setup. -k would hide it, which is exactly why -k is not a test of TLS.

Step 6 — Redirect HTTP to HTTPS

At this point plain HTTP gets a 404, because no route is attached to the http listener. Add the redirect route:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-to-https
namespace: storefront
spec:
parentRefs:
- name: storefront-gateway
sectionName: http
hostnames:
- storefront.local
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
kubectl apply -f 3-http-to-https.yaml
curl -i -H "Host: storefront.local" http://localhost:8888/products?page=2
HTTP/1.1 301 Moved Permanently
location: https://storefront.local/products?page=2

The path and the query string are kept. The port is gone, because when the scheme changes to https and no port is set, the redirect uses the well-known port 443. Through the port-forward that address is not reachable, but on a real cluster with a LoadBalancer it is exactly what you want. You could set port: 8443 in the filter for local testing, but then the manifest no longer matches production, so leave it out.

Step 7 — Install cert-manager with Gateway API Support

Manual certificates expire in a year and nobody remembers to renew them. Install cert-manager from its OCI chart. crds.enabled=true installs cert-manager's own CRDs, and config.gatewayAPI.enabled=true turns on the gateway-shim.

helm upgrade --install cert-manager oci://quay.io/jetstack/charts/cert-manager \
--version v1.21.2 \
--namespace cert-manager --create-namespace \
--set crds.enabled=true \
--set config.gatewayAPI.enabled=true
kubectl get pods -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-7b9c5f6d4-m4w2n 1/1 Running 0 40s
cert-manager-cainjector-5d8f9b7c6-p8x7r 1/1 Running 0 40s
cert-manager-webhook-6c7d8b9f5-q2l9z 1/1 Running 0 40s

If you ever install the Gateway API CRDs after cert-manager, run kubectl rollout restart deployment cert-manager -n cert-manager so it notices them.

Step 8 — Build a CA Issuer Inside the Cluster

This is the cert-manager equivalent of Step 2. A selfSigned ClusterIssuer creates a CA certificate, and a ca ClusterIssuer signs server certificates with it. A ClusterIssuer reads its CA Secret from the cert-manager namespace, so the CA Certificate goes there.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-bootstrap
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: storefront-dev-ca
namespace: cert-manager
spec:
isCA: true
commonName: Storefront Dev CA (cert-manager)
secretName: storefront-dev-ca
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: selfsigned-bootstrap
kind: ClusterIssuer
group: cert-manager.io
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: storefront-dev-ca
spec:
ca:
secretName: storefront-dev-ca
kubectl apply -f 4-dev-ca-issuer.yaml
kubectl get clusterissuer
NAME READY AGE
selfsigned-bootstrap True 12s
storefront-dev-ca True 12s

Step 9 — Let the Gateway Ask for Its Own Certificate

Add one annotation to the Gateway and point the HTTPS listener at a new Secret name, storefront-cm-tls. You do not create that Secret. cert-manager does.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: storefront-gateway
namespace: storefront
annotations:
cert-manager.io/cluster-issuer: storefront-dev-ca
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
hostname: storefront.local
allowedRoutes:
namespaces:
from: Same
- name: https
protocol: HTTPS
port: 443
hostname: storefront.local
tls:
mode: Terminate
certificateRefs:
- kind: Secret
group: ""
name: storefront-cm-tls
allowedRoutes:
namespaces:
from: Same
kubectl apply -f 5-storefront-gateway-cert-manager.yaml
kubectl get certificate -n storefront
NAME READY SECRET AGE
storefront-cm-tls True storefront-cm-tls 9s

The Certificate is named after the Secret and owned by the Gateway. Look at what cert-manager put on it:

kubectl get certificate storefront-cm-tls -n storefront -o jsonpath='{.spec.dnsNames}{"\n"}{.spec.issuerRef.name}{"\n"}{.status.notAfter}{"\n"}{.status.renewalTime}{"\n"}'
["storefront.local"]
storefront-dev-ca
2027-01-19T10:42:17Z
2026-12-20T10:42:17Z

The DNS name came from the listener's hostname. The default certificate lasts 90 days and is renewed at two thirds of its lifetime. You never touch it again.

Step 10 — Verify the New Certificate

Extract the cluster CA so curl can trust it, then call the same URL as before. The port-forward from Step 5 is still running.

kubectl get secret storefront-dev-ca -n cert-manager -o jsonpath='{.data.ca\.crt}' | base64 -d > cm-ca.crt
curl -v --resolve storefront.local:8443:127.0.0.1 --cacert cm-ca.crt https://storefront.local:8443/ 2>&1 | grep -E "issuer:|verify ok|servedByPod"
* issuer: CN=Storefront Dev CA (cert-manager)
* SSL certificate verify ok.
{"servedByPod":"kiosk-api-6d9c8f7b5-7hn4c","podIp":"10.244.0.33","node":"minikube","path":"/"}

The issuer changed and the routes did not. The HTTPRoutes never mention certificates, so swapping how the certificate is produced has no effect on routing. Delete the old hand-made Secret, which nothing references any more:

kubectl delete secret storefront-tls -n storefront

Step 11 — The Production Issuer

On a cluster that is reachable from the internet, with storefront.example.com pointing at the Gateway's external address, only the issuer changes. This ClusterIssuer uses Let's Encrypt with the Gateway API HTTP-01 solver:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
gatewayHTTPRoute:
parentRefs:
- name: storefront-gateway
namespace: storefront
kind: Gateway

Then the Gateway annotation becomes cert-manager.io/cluster-issuer: letsencrypt-prod and both listener host names become storefront.example.com. When the Certificate is requested, follow it through the ACME resources:

kubectl get certificaterequest,order,challenge -n storefront
kubectl get httproute -n storefront

While the challenge is pending you will see a temporary HTTPRoute named cm-acme-http-solver-... attached to the http listener. It disappears when the Order is valid. Use the Let's Encrypt staging server, https://acme-staging-v02.api.letsencrypt.org/directory, while you experiment; the production server rate-limits failed attempts.

Do not apply this file on minikube. The challenge will stay pending forever, because Let's Encrypt cannot reach your laptop.

Step 12 — Clean Up

Remove the routes and restore the plain HTTP Gateway that the next article starts from. cert-manager itself can stay installed; later articles do not depend on it, and it costs nothing while idle.

Remove the issuer annotation from the Gateway before deleting the Certificate. While the annotation is there, the gateway-shim notices the missing Certificate and creates it again within seconds.

kubectl delete httproute kiosk-api http-to-https -n storefront
kubectl annotate gateway storefront-gateway -n storefront cert-manager.io/cluster-issuer-
kubectl delete certificate storefront-cm-tls -n storefront
kubectl delete secret storefront-cm-tls -n storefront
kubectl delete clusterissuer storefront-dev-ca selfsigned-bootstrap
kubectl delete certificate storefront-dev-ca -n cert-manager
kubectl delete secret storefront-dev-ca -n cert-manager

Re-apply the HTTP-only Gateway from the earlier articles (one http listener on port 80, no annotation) and the routes you need. To remove cert-manager completely, run helm uninstall cert-manager -n cert-manager.

Summary

  1. An HTTPS listener with tls.mode: Terminate decrypts traffic at the Gateway. Your .NET Pods keep serving plain HTTP on 8080 and never see a certificate.
  2. certificateRefs points at a kubernetes.io/tls Secret. Keep it in the Gateway's namespace; another namespace needs a ReferenceGrant.
  3. ResolvedRefs=False on a listener almost always means the Secret is missing, malformed, or in a namespace the Gateway may not read.
  4. Test with curl --resolve host:port:127.0.0.1 --cacert ca.crt. -k proves routing, not TLS.
  5. cert-manager watches Gateways once config.gatewayAPI.enabled=true is set. One cert-manager.io/cluster-issuer annotation creates a Certificate for every qualifying listener.
  6. A listener qualifies only with a hostname, tls.mode: Terminate and a Secret name in its own namespace. Anything else is skipped silently.
  7. For Let's Encrypt, the HTTP-01 gatewayHTTPRoute solver attaches a temporary HTTPRoute to an existing port 80 listener through parentRefs.
  8. An HTTP to HTTPS redirect is an HTTPRoute on the http listener with a RequestRedirect filter, scheme: https and statusCode: 301. Attach the application route to https only.

In the next article we stop routing everything to one backend and look at what HTTPRoute can really match on: paths, headers, query parameters and methods, plus the filters that rewrite, redirect and mirror requests on the way through.