Until I fixed it, anyone who typed http://trickspanda.com went through two redirects before reaching a page: first to https://trickspanda.com, then to https://www.trickspanda.com. One Cloudflare redirect rule fixed it. Every version of the domain now reaches the right URL in at most one 301, with the path and query string kept. This post shows the rule, how to add it in the dashboard or through the API, and how to check the result with curl.
Tested 27 October 2026 on trickspanda.com: Cloudflare free plan, proxied DNS, SSL mode Full (strict), Always Use HTTPS on, WordPress with its Site Address set to www.
Two hops, and who makes each one
To see the chain, request a URL on the bare domain and follow the redirects with curl. This is the output from before the fix, with only the useful headers kept:
$ curl -sIL "http://trickspanda.com/set-value-unchecked-checkbox/?ref=x" | grep -iE '^(HTTP|location|x-redirect-by)'
HTTP/1.1 301 Moved Permanently
Location: https://trickspanda.com/set-value-unchecked-checkbox/?ref=x
HTTP/2 301
location: https://www.trickspanda.com/set-value-unchecked-checkbox/?ref=x
x-redirect-by: WordPress
HTTP/2 200Code language: Bash (bash)
The headers tell you who sent each redirect:
- Hop 1 is Cloudflare. Always Use HTTPS answers the plain-HTTP request at the edge and keeps the host, so it only changes
httptohttps. - Hop 2 is WordPress. The
x-redirect-by: WordPressheader gives it away. WordPress’s Site Address ishttps://www.trickspanda.com, so it sends any request for another host to the www version. That request has to travel all the way to the origin server, and PHP has to run, just to answer “go somewhere else”.
Visitors and crawlers do get to the page, so this isn’t an emergency. But every hop is another round trip, and links to the bare domain pass through two redirects instead of one. The aim is one hop, answered by Cloudflare, so WordPress never sees the request.
The rule
Cloudflare’s Redirect Rules (the product the API calls Single Redirects) can match the bare hostname and build the target from the request path:
When: (http.host eq "trickspanda.com")
Then: dynamic redirect to
concat("https://www.trickspanda.com", http.request.uri.path)
Status: 301
Preserve query string: onCode language: plaintext (plaintext)
http.host eq "trickspanda.com"matches the bare domain only. The www host and other subdomains don’t match, so they behave as before.- The target is hard-coded to
https://, so one rule covers bothhttp://andhttps://requests to the bare domain. http.request.uri.pathis the path without the query string. “Preserve query string” adds the query back, so?utm_source=…and similar parameters survive the redirect.
Cloudflare’s own documented example for this redirect matches the request URL https://example.com/*, so it leaves plain-HTTP requests to Always Use HTTPS, and those still take two hops. Matching on http.host covers both schemes.
The thing I wasn’t sure about was whether Always Use HTTPS would still answer first on a plain-HTTP request and keep the extra hop. It didn’t: with both turned on, the redirect rule answered http://trickspanda.com/… directly with the final www URL. Cloudflare’s documentation doesn’t spell out the order, so check it on your own zone with the curl loop below.
Adding it in the dashboard
- Open the zone, go to Rules > Overview and choose Create rule > Redirect Rule.
- Give it a name, then under When incoming requests match choose Custom filter expression and set Hostname equals your bare domain.
- Under Then, make the target URL dynamic and enter
concat("https://www.yourdomain.com", http.request.uri.path). - Set the status code to 301, turn on Preserve query string and choose Deploy.
The rule only runs on proxied (orange-cloud) traffic, so the DNS record for the bare domain has to be proxied. The Free plan includes 10 Single Redirect rules (Pro 25, Business 50), and this setup uses one.
Adding it through the API
I added it with the API because I manage several zones from scripts. The token needs the zone-level Single Redirect: Edit permission, and the zone has to be in that permission’s zone resources. My first attempt failed because the token didn’t include this zone yet: the call returned 403 with "request is not authorized". Once the zone was added, the same GET returned 404 (code 10003, “could not find entrypoint ruleset”). That is normal on a zone that has never had a redirect rule, and the PUT below creates the entrypoint.
Save the rule as redirect.json:
{
"rules": [{
"description": "apex to www, one hop",
"expression": "(http.host eq \"trickspanda.com\")",
"action": "redirect",
"action_parameters": {
"from_value": {
"status_code": 301,
"target_url": {
"expression": "concat(\"https://www.trickspanda.com\", http.request.uri.path)"
},
"preserve_query_string": true
}
},
"enabled": true
}]
}Code language: JSON / JSON with Comments (json)
Then send it to the zone’s redirect phase:
curl -X PUT \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_request_dynamic_redirect/entrypoint" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data @redirect.jsonCode language: Bash (bash)
Careful: PUT on the entrypoint replaces every redirect rule in the zone with the list you send. That is fine on a zone with no redirect rules. If yours already has some, GET the entrypoint first and use POST on /rulesets/{ruleset_id}/rules to add a single rule.
Checking the result
Test with curl rather than a browser: browsers cache 301s, so an old redirect can hide your change. The same curl request now takes one hop:
$ curl -sIL "http://trickspanda.com/set-value-unchecked-checkbox/?ref=x" | grep -iE '^(HTTP|location|x-redirect-by)'
HTTP/1.1 301 Moved Permanently
Location: https://www.trickspanda.com/set-value-unchecked-checkbox/?ref=x
HTTP/2 200Code language: Bash (bash)
To test every variant in one go, count the redirects for each one:
for u in "http://trickspanda.com/" \
"https://trickspanda.com/hermes-agent-nas/" \
"http://trickspanda.com/hermes-agent-nas/?utm_source=test" \
"https://www.trickspanda.com/" \
"https://www.trickspanda.com/"; do
curl -s -o /dev/null -L -w "%{num_redirects} hop(s) -> %{url_effective}\n" "$u"
doneCode language: Bash (bash)
1 hop(s) -> https://www.trickspanda.com/
1 hop(s) -> https://www.trickspanda.com/hermes-agent-nas/
1 hop(s) -> https://www.trickspanda.com/hermes-agent-nas/?utm_source=test
1 hop(s) -> https://www.trickspanda.com/
0 hop(s) -> https://www.trickspanda.com/Code language: plaintext (plaintext)
http://www… still takes one hop, made by Always Use HTTPS, which is what you want. The canonical https://www… URL takes none. If any line shows 2, look at its location and x-redirect-by headers to see which layer is still adding a hop.
If your site lives on the bare domain
Reverse it: match http.host eq "www.yourdomain.com" and use concat("https://yourdomain.com", http.request.uri.path) as the target. Whichever way you go, make the rule’s target the same as the URL your CMS treats as canonical (in WordPress, the Site Address under Settings > General). Otherwise the rule and WordPress send visitors back and forth and you get a redirect loop.
