Skip to the article

Put Your Own Domain Behind a Printed QR Code (and Change Where It Points Later)

Published · Reviewed

A printed QR code can point at an address on a domain you own, such as example.com/qr/menu, and your server can redirect that address to the real page. When the real page changes, you edit one redirect rule instead of reprinting. Start with a temporary redirect (302 or 307), because a lasting one (301 or 308) is cached by clients and is harder to take back. [S9]

This guide is for the self-run route: you already own a domain, or are willing to, and you want the printed address to stay yours. It covers choosing the address, picking the status code, copy-ready rules for Cloudflare, Netlify, Vercel and Apache, a test routine for before the print run, and the ways a self-run redirect can still fail. If you would rather not run anything, a hosted editable code does the same job from a web page; the last section compares the two. For what actually makes a printed code stop working, see do QR codes expire.

What you are really building

A standard QR code stores text. For a web link, that text is the address. The code is not "dynamic" or "static" on its own; what changes is whether the address inside it can be pointed somewhere else later. If the address belongs to a domain you control, you decide where it goes, and all four platforms covered here let you do that with a short rule. [S1][S5][S8][S10] (For the wider static-versus-dynamic picture, see static vs dynamic QR codes.)

The setup has three parts:

  1. A short address on your domain, for example https://example.com/qr/menu. This is what gets encoded and printed.
  2. A redirect rule on whatever serves that domain. It answers requests for /qr/menu with a status code and a Location header.
  3. The destination, for example https://www.example.com/dining/menu-2026. This is the part you can change at any time.

Pick the address before you print

The address is the one thing you cannot change after printing, so spend the time here.

  • Use HTTPS, lowercase letters, digits and hyphens. No spaces, no capital letters. Rules on Netlify and Apache match paths case-sensitively, and Vercel's own documentation says its CDN does not lower-case paths, so /QR/Menu and /qr/menu are different addresses. [S5][S10][S9]
  • Keep it short. A shorter address means a smaller, less dense code at the same print size. In an offline check with an open QR encoder, a 21-byte address needed version 3 (29 by 29 modules) at error correction H, while a 105-byte address with tracking parameters needed version 10 (57 by 57 modules). [T2]
  • Give each placement its own path if you want to tell them apart: /qr/menu-table, /qr/menu-window. Rules are cheap; reprints are not.
  • Do not put anything private in the path. Anyone who scans can read it.
Address printed in the codeBytesMin. version at level LMin. version at level H
https://example.com/m212 (25 modules)3 (29 modules) [T2]
https://go.example.com/menu272 (25 modules)4 (33 modules) [T2]
https://example.com/qr/spring-menu-2026393 (29 modules)5 (37 modules) [T2]
A 64-byte final page URL644 (33 modules)7 (45 modules) [T2]
A 105-byte final URL with UTM parameters1055 (37 modules)10 (57 modules) [T2]

These figures come from one encoder run on example addresses (reserved example.com names); they show the trend, not a scanning guarantee. [T2]

301 or 302 for a printed code?

The status code influences how a redirect may be reused, but actual caching also depends on response headers and client behavior.

CodeMeaningCache behavior under HTTPNotes
302Temporary, method may change to GETNot heuristically cacheable by default; may be cached with explicit freshnessNetlify supports it; Apache's Redirect uses it by default [S6][S10][S9][S18]
307Temporary, method preservedNot heuristically cacheable by default; may be cached with explicit freshnessNetlify does not currently support 307; Vercel sends it when permanent is false [S6][S8][S9][S18]
301Moved for good, method may change to GETHeuristically cacheable unless controlled otherwiseNetlify's default when you leave the status out [S6][S9][S17]
308Moved for good, method preservedHeuristically cacheable unless controlled otherwiseVercel sends it when permanent is true, which is the default [S8][S9][S17]

For a QR code that opens a web page via GET, the method-preservation distinction usually does not affect that scan. Caching and search-engine treatment matter more. MDN explains that 301/308 indicate a permanent move while 302/307 signal a temporary destination. [S12] Vercel's documentation describes its typical client-caching behavior, but that description is not a universal promise that 302 or 307 can never be cached. [S9]

RFC 9110 treats 301 and 308 as heuristically cacheable. RFC 9111 permits caching of other redirect responses when the relevant conditions and explicit freshness information are present. Check the actual response's Cache-Control and the behavior of the clients you support. Choosing 302 reduces one class of persistent-redirect risk; it does not guarantee instant changes for every prior scanner. [S17][S18]

Our reasoning, which is untested in browsers: if a phone reuses a lasting redirect and you later point the rule somewhere else, some people who scanned before may keep landing on the old page. No source was found for how long browsers keep such an entry, so treat it as an unknown rather than a number. [S13] The safe habit is to start with 302 (or 307), switch to 301 only when you are sure the destination will never change, and for a code on paper that will hang on a wall for years, simply stay on 302. [S9]

Two traps worth knowing:

  • On Vercel, if you leave out permanent, you get a 308, because the documented default is true. [S8]
  • On Netlify, if you leave out the status, you get a 301. [S6]

Both defaults select heuristically cacheable redirect statuses, although actual reuse still depends on caching controls and client behavior. Always write the status explicitly. [S17][S18]

Copy-ready rules by platform

All snippets use example.com placeholders. Replace the path and the destination with yours. Only the Apache sample was executed (see the test section); the other three follow the vendor documentation read on 2026-10-25 and were not run against a real account.

Cloudflare Single Redirects

Cloudflare configures this in the dashboard: open the Rules Overview page, then choose Create rule and Redirect Rule. The hostname must be proxied through Cloudflare, otherwise the rule does not see the request. [S3][S4]

FieldValue for a fixed path
When incoming requests matchCustom filter expression: http.request.uri.path eq "/qr/menu" [S2]
Then, typeStatic
URLhttps://www.example.com/dining/menu-2026
Status code302 if you may change it later (Cloudflare's settings page lists 301, 302, 307 and 308) [S15]
Preserve query stringOn, if you add tracking parameters to the printed address (the setting is off by default) [S15]

For a whole prefix, Cloudflare's wildcard interface lets you write the request URL with an asterisk and reuse the captured text as ${1} in the target: [S3]

Request URL:  https://example.com/qr/events/*
Target URL:   https://www.example.com/events/${1}

The wildcard syntax comes from Cloudflare's documented pattern; this exact rule is our adaptation and is untested. [S3] The documented example uses status 301, so choose 302 yourself when you want the changeable kind. [S2][S15] If you have several rules, the first matching one is used, and Single Redirects have a per-zone rule limit that depends on your plan (10 on the free plan according to the overview page read on 2026-10-25). [S4]

Netlify _redirects

Create a plain-text file called _redirects (no extension) in your publish directory. One rule per line: path, target, optional status. # starts a comment, the first matching rule from the top wins, and paths are case-sensitive. [S5]

# fixed path, temporary
/qr/menu      https://www.example.com/dining/menu-2026   302

# prefix: everything after /qr/events/ is carried over
/qr/events/*  https://www.example.com/events/:splat      302

# only when the destination will never change again
/old-menu     https://www.example.com/dining/menu        301

Netlify passes query parameters through for 200, 301 and 302 rules, the asterisk can only sit at the end of a path segment, and the splat is written :splat. [S6] Netlify's page also says paths match rules whether or not they have a trailing slash, so /qr/menu/ and /qr/menu reach the same rule. [S6] Print the form you tested anyway.

Vercel vercel.json

Put a redirects array in vercel.json at the project root. Each entry has source and destination; the status comes from either permanent or statusCode, but not both. [S8]

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "redirects": [
    { "source": "/qr/menu", "destination": "https://www.example.com/dining/menu-2026", "statusCode": 302 },
    { "source": "/qr/events/:path*", "destination": "https://www.example.com/events/:path*", "permanent": false }
  ]
}

The first entry uses statusCode for an explicit 302. [S8] When permanent is false, Vercel sends a 307, and the second entry does that. [S8] Remember the default: an entry with neither field is a 308. [S8] The array can hold up to 2,048 redirects. [S7] The wildcard form :path* follows Vercel's documented pattern; this exact entry is untested. A redirect in vercel.json changes with a new deployment.

Vercel also documents project-level redirects managed with the vercel redirects command (statuses 301, 302, 307 or 308, optional query-string preservation), which it says are updated without a new deployment. [S16] For a printed code whose destination will change, that second route is worth reading about before you commit to editing a file each time.

Apache mod_alias

On a server you manage, put this in the main configuration, or in .htaccess if your host allows it. Apache recommends the main configuration when you can edit it, and a .htaccess file is ignored unless the directory's AllowOverride setting enables it (the 2.4 default is None). [S11] In the local test below the rules worked with AllowOverride FileInfo and were ignored under None. [T1]

# fixed path, temporary (Redirect without a status also defaults to 302)
Redirect temp "/qr/menu" "https://www.example.com/dining/menu-2026"

# prefix: /qr/events/anything -> https://www.example.com/events/anything
Redirect temp "/qr/events" "https://www.example.com/events"

# only when the destination will never change again
Redirect permanent "/old-menu" "https://www.example.com/dining/menu"

The directive matches whole path segments as a prefix, appends any extra path to the target, keeps the query string, and is case-sensitive. [S10]

Test before the print run

From a terminal, ask for the headers only and look at the status and the Location line:

curl -sI "https://example.com/qr/menu?utm_source=poster" | grep -iE "^(HTTP|location)"

You want the status you configured (302 or 307 here) and a Location that is your real destination with the query string still attached. To see the whole chain and the final address:

curl -sIL -o /dev/null -w "%{http_code} %{url_effective}\n" "https://example.com/qr/menu"

Vercel's own guide uses curl -I for the same purpose and expects the configured status plus a Location header. [S9] Then check four things:

  • One hop only. The destination should be the final HTTPS address, not another redirect (for example http to https to www). Every extra hop is one more place to break.
  • Query string. Test with ?x=1 and confirm it arrives (Netlify and Apache document that parameters pass through [S6][S10]; Vercel's redirects guide shows a vercel.json redirect where query parameters pass through unless the destination replaces them [S9]; Cloudflare has a "preserve query string" option [S3]), or confirm it is dropped if you set it that way.
  • Case and trailing slash. Try /QR/menu and /qr/menu/ and decide which forms you accept.
  • A real phone on mobile data. Print the code at its final size on the final material, scan it with the phone's camera with Wi-Fi switched off, and confirm the right page opens.

What was actually run: a small test script on 2026-10-25 against a local Apache 2.4.67 bound to 127.0.0.1. [T1] It confirmed the sample rules return 302 for temp, 301 for permanent, keep the query string, carry the extra path on prefix rules, ignore /qr/menufoo (whole segments only), ignore /QR/menu (case-sensitive), and that a .htaccess file is ignored under AllowOverride None: 22 checks passed. [T1] That is a local server, not a hosting platform, and no phone was involved. Cloudflare, Netlify and Vercel were not run.

What can still break a self-run redirect

  • The domain. The printed code depends on your registration, your DNS host and your web host staying in working order. If the domain lapses, every code pointing at it stops working with it. What happens during grace or redemption periods differs by registrar and registry, and we did not verify those rules, so do not plan around them. Turn on auto-renew and a registrar lock, and put the renewal date somewhere other than one person's inbox.
  • Cloudflare proxying. If you later turn off proxying for the hostname, the Single Redirects rule stops seeing the traffic. [S3]
  • Platform limits and docs change. Plans cap the rule count (Cloudflare lists per-plan numbers; Vercel caps the array at 2,048). [S4][S7] Vendors move and revise their documentation, which is why the snippets above carry their read date. Re-check them every few months.
  • Whoever inherits it. A redirect nobody remembers is a redirect nobody renews. Keep a one-page register:
Printed address and placementCurrent destination and statusPlatform and rule locationRenewal date and owner
https://example.com/qr/menu on table cardshttps://www.example.com/dining/menu-2026, 302Cloudflare, Redirect Rules(date), (name)
  • Statistics. A bare redirect rule gives you no scan report. Some platforms log or chart redirect traffic (Vercel's guide points to an Observability tab with request counts for redirected routes [S9]), but we did not check the others, so plan your measurement separately.

Self-run redirect or a hosted editable code?

Both approaches solve "change it after printing". They differ in who runs the redirect.

AspectRedirect on your own domainOwnQR editable code
Domain a scanner sees firstYour ownownqrcode.com/r/<slug>, a random short slug assigned by OwnQR; you cannot choose it
Who changes the destinationYou edit a rule, then deploy or saveThe signed-in owner changes the destination for that code; for a link code the destination must be an http or https address
What the code depends onYour domain, DNS, hosting and the platform's rulesOwnQR's redirect service on every scan
CostDomain and hosting you already pay for, plus your timeUS$15 for one code or US$39 for five, paid once, as read from the product configuration on 2026-10-24 and not re-checked against live billing
Scan statisticsDepends on your platform (not checked here)Detailed scan analytics are limited to paid codes; see the pricing page for what is included
Moving away laterThe address on the print is yours, so you can point it at any destination or hostThe code carries OwnQR's address, so a printed code cannot be moved to another provider without reprinting
Best whenYou already run a domain and a site, want your own address on the print, and are comfortable with a config changeYou want to edit from a web page without touching DNS or code

As far as the product code read on 2026-10-24 shows, OwnQR has no self-serve custom domain option (not tested on the live site); the only branded domains in the code are ones set up by hand for specific customers, and there is no custom slug input. The pricing page also invites enquiries about branded domains, quoted per project; that is a custom arrangement we did not evaluate. So for a scan address on your own domain that you set up yourself, OwnQR is not the right choice, and a hosted code is more than you need if you already run a site and a domain with someone responsible for them. Pointing a hosted OwnQR code at a page on your own domain is a different thing and works, but the printed address is still OwnQR's.

If you do use a hosted code, read how the service handles the end of its own life: OwnQR's public terms (linked in the sources) contain a Sunset Pledge for Lifetime customers (sections 5.1 and 5.1a), and it is a promise in the Terms, not a feature you can use today: no self-serve export appears in the product code. [S14] Whether a Creator Pack buyer counts as a Lifetime customer under the Terms is not confirmed here. The pledge covers Lifetime customers only; for what that offer includes, see lifetime QR codes. Current prices are on the OwnQR pricing page.

Checklist before you send the file to print

  • The printed address is HTTPS, lowercase, short, and unique per placement.
  • The rule uses an explicit 302 (or 307) status, not a platform default.
  • curl -I shows the status and a Location that is the final destination, in one hop, with the query string you expect.
  • A phone on mobile data opens the right page from the actual printed proof.
  • Domain auto-renew is on, and the register above has an owner and a renewal date.
  • You know where the rule lives and who can edit it.

Sources

  1. [S1] Cloudflare Docs - Single Redirects
    https://developers.cloudflare.com/rules/url-forwarding/single-redirects/ · accessed
  2. [S2] Cloudflare Docs - Redirect a path to a new URL (example)
    https://developers.cloudflare.com/rules/url-forwarding/examples/redirect-new-url/ · accessed
  3. [S3] Cloudflare Docs - Create a Single Redirect in the dashboard
    https://developers.cloudflare.com/rules/url-forwarding/single-redirects/create-dashboard/ · accessed
  4. [S4] Cloudflare Docs - URL forwarding overview
    https://developers.cloudflare.com/rules/url-forwarding/ · accessed
  5. [S5] Netlify Docs - Redirects overview
    https://docs.netlify.com/manage/routing/redirects/overview/ · accessed
  6. [S6] Netlify Docs - Redirect options (page shows Last updated Sep 17, 2026)
    https://docs.netlify.com/manage/routing/redirects/redirect-options/ · accessed
  7. [S7] Vercel Docs - Configuration redirects
    https://vercel.com/docs/routing/redirects/configuration-redirects · accessed
  8. [S8] Vercel Docs - Static configuration with vercel.json (page marked last updated 2026-08-14)
    https://vercel.com/docs/project-configuration/vercel-json · accessed
  9. [S9] Vercel Docs - Redirects (page marked last updated 2026-09-18)
    https://vercel.com/docs/routing/redirects · accessed
  10. [S10] Apache HTTP Server 2.4 - mod_alias
    https://httpd.apache.org/docs/current/mod/mod_alias.html · accessed
  11. [S11] Apache HTTP Server 2.4 - .htaccess howto
    https://httpd.apache.org/docs/current/howto/htaccess.html · accessed
  12. [S12] MDN - Redirections in HTTP
    https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Redirections · accessed
  13. [S13] MDN - 301 Moved Permanently
    https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/301 · accessed
  14. [S14] OwnQR Terms (page shows last updated 2026-04-06)
    https://ownqrcode.com/en/terms · accessed
  15. [S15] Cloudflare Docs - Single Redirects settings
    https://developers.cloudflare.com/rules/url-forwarding/single-redirects/settings/ · accessed
  16. [S16] Vercel Docs - vercel redirects (CLI; page marked last updated 2026-05-28)
    https://vercel.com/docs/cli/redirects · accessed
  17. [S17] RFC 9110 - HTTP Semantics (sections 15.1, 15.4.2 and 15.4.9; plain-text edition re-read 2026-10-25)
    https://www.rfc-editor.org/rfc/rfc9110.html · accessed
  18. [S18] RFC 9111 — HTTP Caching, Sections 3 and 4.2.2
    https://www.rfc-editor.org/rfc/rfc9111.html · accessed

Our own checks

  1. [T1] We ran the sample Apache rules on a local test server (22 checks). We did not run the Cloudflare, Netlify or Vercel rules on real accounts. The script and its output are not public yet, and the result has not been independently verified.
  2. [T2] With a local QR encoder we worked out the smallest QR version that fits each address length at error-correction levels L and H. The script and its output are not public yet, and the result has not been independently verified.