How to set up a custom domain and HTTPS redirects on Cloud Run
It's been a few years now, but I'm still excited by how Cloud Run can give you an HTTPS
endpoint on the *.run.app domain in just a few steps. When you want to use
a custom domain instead of the default URL, you have multiple options, including
Firebase Hosting, Cloud Load Balancing, and a third party CDN. There's also the Custom
Domain feature in Cloud Run, but the documentation says it's a preview feature and not
recommended for production right now.
I'll show you how to set up a Cloud Run service with a custom domain through Cloud Load Balancing.
Through Cloud Load Balancing, you can set up an external HTTP(S) application load balancer. It doesn't have a free tier, but it allows you to add Cloud CDN caching, add Google Cloud Armor, create Google-managed SSL certificates, and much more. Refer to the Application Load Balancer overview in the Google Cloud documentation to learn more.
Here's what the steps are. First, enable the required APIs, reserve a static IP and update your DNS records. Then, start the provisioning process of a managed SSL certificate. While that's running, deploy a Cloud Run service, and setup the load balancer in front of it, including injected HSTS headers and the HTTP-to-HTTPS redirect.
Prerequisites
Before you start, make sure you have:
- A domain name you control.
- The
gcloudCLI installed and authenticated. - A Google Cloud project with an attached billing account.
1. Enable the required APIs
Enable the Compute Engine (for the load balancer) and Cloud Run APIs in your Google Cloud project.
gcloud services enable compute.googleapis.com run.googleapis.com
2. Reserve a static IP and update DNS
The next step is to reserve a global static IP address and point your domain to it.
Reserve a static IP address
gcloud compute addresses create lb-ip --global
To see your IP address, run:
gcloud compute addresses describe lb-ip --global --format='get(address)'
Update your DNS
Go to your DNS provider and point your domain's A record to the static IP
address you just reserved. By pointing your domain to the load balancer's IP, you give
Google the proof it needs to issue a certificate for that domain. It's how you prove you
own the domain.
Note that Google's validation service must be able to reach your load balancer's IP address directly. Intermediate layers such as a third-party CDN can cause the certificate provisioning to fail.
If Load Balancer Authorization doesn't work for you, there's an alternative way to prove ownership that requires creating a CNAME record for validation but offers more flexibility for complex configurations.
3. Provision a managed SSL certificate
To secure your traffic with HTTPS, you need an SSL certificate. Google Cloud handles
provisioning and renewal for you. Replace [DOMAIN_NAME] with your own
domain in the command below.
gcloud compute ssl-certificates create lb-cert \
--domains=[DOMAIN_NAME] \
--global
The certificate stays in a PROVISIONING state until the load balancer is
live (you'll create it soon) and DNS is propagated. It typically takes up to 60 minutes
to become ACTIVE once the configuration is complete.
4. Deploy an example Cloud Run service
Now, deploy a web service. I'm using the official NGINX image as an example here. It's a web server, which works great to prove this configuration works. You can deploy your own app later.
gcloud run deploy run-service \
--image=nginx \
--region=europe-west1 \
--allow-unauthenticated \
--port=80
The --allow-unauthenticated flag makes the service publicly accessible. The
--port=80 flag is required because the NGINX image listens on port 80,
whereas Cloud Run defaults to port 8080.
5. Configure a serverless backend
A serverless backend is a global resource and connects Cloud Load Balancing to your Cloud Run service.
Set up the backend service
The backend service acts as a central configuration point where you enable features like Cloud CDN caching, Cloud Armor, and custom headers.
gcloud compute backend-services create backend-service \
--load-balancing-scheme=EXTERNAL_MANAGED \
--custom-response-header='Strict-Transport-Security: max-age=31536000; includeSubDomains; preload' \
--global
The --custom-response-header flag instructs the load balancer to
automatically inject the Strict-Transport-Security HTTP header into all
responses sent back to the client. This security header tells browsers to never use HTTP
to make requests, preventing
downgrade attacks.
Create a serverless network endpoint group
Now that you have created a backend service, you can connect the Cloud Run service to it. Cloud Load Balancing requires a regional configuration object for it, a serverless network endpoint group (NEG). This object represents the Cloud Run service within Cloud Load Balancing.
gcloud compute network-endpoint-groups create run-neg \
--region=europe-west1 \
--network-endpoint-type=serverless \
--cloud-run-service=run-service
Add the serverless NEG to the backend service
Plug in the Cloud Run service to the backend using the NEG.
gcloud compute backend-services add-backend backend-service \
--global \
--network-endpoint-group=run-neg \
--network-endpoint-group-region=europe-west1
Create the URL map
The URL map acts as the router for your load balancer. It inspects the hostname and path of incoming requests and directs them to the appropriate backend service. This configuration specifies to forward all requests to the backend service.
gcloud compute url-maps create url-map \
--default-service=backend-service
6. Set up the HTTPS frontend
The frontend configuration defines how clients reach your service over HTTPS. It consists of a target proxy and a forwarding rule.
Create the target HTTPS proxy
The target HTTPS proxy terminates the SSL connection using the certificate you created in 3. Provision a managed SSL certificate.
gcloud compute target-https-proxies create https-proxy \
--ssl-certificates=lb-cert \
--url-map=url-map
Create the HTTPS forwarding rule
The forwarding rule connects your static IP address and port 443 (HTTPS) to the target proxy.
gcloud compute forwarding-rules create https-rule \
--load-balancing-scheme=EXTERNAL_MANAGED \
--network-tier=PREMIUM \
--address=lb-ip \
--target-https-proxy=https-proxy \
--global \
--ports=443
7. Set up the HTTP-to-HTTPS redirect
The HSTS header tells browsers to always use HTTPS. However, you still need a redirector to catch and upgrade a clients very first unencrypted request, in case they request the HTTP endpoint.
Create the redirect URL map
Use a special URL map that returns a 301 Moved Permanently redirect.
gcloud compute url-maps import redirect-map \
--global \
--source /dev/stdin <<EOF
name: redirect-map
defaultUrlRedirect:
redirectResponseCode: MOVED_PERMANENTLY_DEFAULT
httpsRedirect: True
EOF
Create the target HTTP proxy
gcloud compute target-http-proxies create http-proxy \
--url-map=redirect-map
Create the HTTP forwarding rule
This rule uses the same static IP as the HTTPS rule, but listens on port 80.
gcloud compute forwarding-rules create http-rule \
--load-balancing-scheme=EXTERNAL_MANAGED \
--address=lb-ip \
--target-http-proxy=http-proxy \
--global \
--ports=80
Verification
Once you have completed the configuration, you can verify that everything is working correctly.
Check certificate status
Google proves your domain ownership by attempting to reach your load balancer from multiple locations across the internet. You can monitor the progress of this automated proof using these commands:
# Check the overall certificate status (PROVISIONING -> ACTIVE)
gcloud compute ssl-certificates describe lb-cert \
--global \
--format="get(name,managed.status)"
# Check the specific domain validation status
gcloud compute ssl-certificates describe lb-cert \
--global \
--format="get(managed.domainStatus)"
If you see FAILED_NOT_VISIBLE, it usually means DNS has not yet propagated
to all of Google's validation vantage points, or your load balancer isn't yet reachable
on port 443.
Test HTTPS and redirects
Once the certificate is ACTIVE, test the endpoint using curl.
# Verify the HTTP to HTTPS redirect (should be 301)
curl -I http://[DOMAIN_NAME]
# Verify the HTTPS response and HSTS header
curl -I https://[DOMAIN_NAME]
In the output for the HTTPS request, you should see the
strict-transport-security header and a 200 OK status.
Conclusion
Setting up a custom domain with an external load balancer takes a few more steps than
the default *.run.app domain, and it's not part of the Google Cloud free
tier. (Refer to the
Cloud Load Balancing pricing
page to learn more.) However, it gives you full control over your traffic and access to
more advanced Google Cloud load balancing features.