Introduction
You've set up a Shopify App Proxy. The external server is running. Direct URL access to your endpoint works perfectly — you hit it in the browser and get the correct response. Your Nginx logs are clean. Everything looks fine.
Except when you access your proxy URL through Shopify — yourstore.myshopify.com/apps/yourproxy/ — you get a 404 Not Found.
And the most baffling part: Nginx shows zero incoming traffic. The request isn't even reaching your server.
This is one of the most disorienting debugging experiences in Shopify app development because every individual component appears to be working. The problem isn't where you're looking — it's in the layers between Shopify and your server that you can't directly observe.
This guide systematically diagnoses every possible cause and walks through the fix for each one.
How Shopify App Proxy Actually Works
Before diagnosing, understand the exact request chain:
Customer browser
↓
yourstore.myshopify.com/apps/yourproxy/path
↓
Shopify's Edge Network
↓ (Shopify rewrites and forwards the request)
your-server.com/proxy/path?shop=yourstore.myshopify.com&...
↓
Your server (Nginx → App)
↓
Response back to Shopify
↓
Shopify forwards response to browser
Key insight: Shopify's edge network is the intermediary. When Nginx shows no incoming traffic, the request is dying between Shopify's edge and your server — it never reaches Nginx at all. This immediately narrows the problem to one of these layers:
- App Proxy configuration in the Partner Dashboard
- DNS resolution of your server's domain
- TLS/SSL certificate validity
- Firewall or network-level blocking of Shopify's IP ranges
- Shopify's edge rejecting the request before forwarding
The "No Nginx Traffic" Diagnostic
The fact that Nginx shows zero incoming requests when the proxy is called is the single most important diagnostic clue in this entire guide. It means:
- ✅ Your app proxy URL is registered with Shopify (otherwise you'd get a different error)
- ✅ Shopify is receiving the request at its edge
- ❌ Shopify's edge is not forwarding the request to your server
This is fundamentally different from a 404 caused by your app returning the wrong response. The request is being killed upstream. Everything from Step 1 through Step 5 below addresses this specific scenario.
Step 1 — Audit Your App Proxy Configuration in Partner Dashboard
The most common cause of a completely silent proxy (no Nginx traffic at all) is a misconfiguration in the Partner Dashboard that causes Shopify to reject or misroute the request before it ever leaves their network.
In your Partner Dashboard:
- Go to Partner Dashboard → Apps → [Your App] → App setup
- Scroll to App Proxy section
- Verify every field precisely:
Subpath Prefix
The Subpath Prefix must be one of Shopify's allowed values:
-
apps✅ -
a✅ -
community✅ -
tools✅
Any other value is not accepted and the proxy will silently fail.
Subpath
This is the unique identifier for your proxy — e.g., myapp. The full URL becomes:
yourstore.myshopify.com/apps/myapp/
- Must be URL-safe (lowercase letters, numbers, hyphens only)
- Must be unique — if another app on the store uses the same subpath, there's a collision
- Must not contain slashes
Proxy URL
This is the destination URL on your server. This field is where most silent failures originate.
Common mistakes in the Proxy URL field:
| Mistake | Example | Problem |
|---|---|---|
| HTTP instead of HTTPS | http://yourserver.com/proxy |
Shopify requires HTTPS |
| Trailing slash missing | https://yourserver.com/proxy vs https://yourserver.com/proxy/ |
Path matching issue in Nginx |
| Wrong port | https://yourserver.com:3000/proxy |
Port blocked by firewall |
| IP address instead of domain | https://203.0.113.5/proxy |
TLS certificate won't validate against IP |
| Non-public domain | https://localhost/proxy |
Not reachable from Shopify's network |
| Typo in domain | https://yoursever.com/proxy |
DNS failure |
Fix: Carefully verify the Proxy URL is:
-
https://(nothttp://) - A fully qualified domain name (not an IP address)
- The correct subdomain/path where your app actually listens
- Reachable from the public internet (not a local or private network)
After any change to the App Proxy configuration, save and wait 60 seconds before testing — Shopify's edge takes time to propagate configuration changes.
Step 2 — Verify Your TLS/SSL Certificate
Shopify's edge network will refuse to forward requests to your proxy URL if the TLS certificate is invalid, expired, self-signed, or mismatched. This is a hard requirement — not optional. And crucially, this failure happens silently at Shopify's edge — no traffic reaches your Nginx server.
Check your certificate:
# Check certificate validity and expiry
openssl s_client -connect yourserver.com:443 -servername yourserver.com \
</dev/null 2>/dev/null | openssl x509 -noout -dates -subject -issuer
# Expected output:
# notBefore=Sep 1 00:00:00 2026 GMT
# notAfter=Sep 1 23:59:59 2027 GMT
# subject=CN=yourserver.com
# issuer=O=Let's Encrypt, CN=R3
What to look for:
-
notAfterdate is in the future — certificate has not expired -
subjectmatches your proxy domain exactly (including subdomain if applicable) -
issueris a trusted CA (Let's Encrypt, DigiCert, Comodo, etc.) — not a self-signed cert - No SSL handshake errors in the output
Check for certificate chain issues:
# Verify full chain
curl -vI https://yourserver.com/proxy 2>&1 | grep -E "SSL|TLS|certificate|expire"
Common certificate problems:
Problem: Self-signed certificate
issuer=CN=yourserver.com ← same as subject = self-signed
Fix: Replace with a Let's Encrypt certificate:
certbot --nginx -d yourserver.com
Problem: Certificate doesn't cover the subdomain
subject=CN=yourserver.com ← doesn't cover api.yourserver.com
Fix: Issue a wildcard or SAN certificate:
certbot --nginx -d yourserver.com -d api.yourserver.com
Problem: Intermediate certificate not served The chain is incomplete — Shopify's edge can't verify trust. Fix in Nginx:
ssl_certificate /etc/letsencrypt/live/yourserver.com/fullchain.pem; # fullchain, not cert.pem
ssl_certificate_key /etc/letsencrypt/live/yourserver.com/privkey.pem;
Always use fullchain.pem, never cert.pem alone.
Online verification: Use
Step 3 — Check Firewall Rules and IP Allowlisting
If your server is behind a firewall that restricts inbound connections by IP address, Shopify's proxy requests will be silently dropped — Nginx never sees them.
Shopify's proxy requests originate from Shopify's edge IP ranges, which are not static or fully published. This means IP-based allowlisting is fundamentally incompatible with Shopify App Proxy.
Check your firewall:
# Check if port 443 is open to the world
sudo ufw status
# or
sudo iptables -L INPUT -n -v | grep 443
Diagnose from an external perspective:
# Test if your server is reachable on 443 from outside your network
# Run this from a different machine or use an online tool
curl -v --connect-timeout 10 https://yourserver.com/proxy
If this times out or refuses connection → firewall is blocking.
Fix — Open port 443 to all IPs:
# UFW
sudo ufw allow 443/tcp
# iptables
sudo iptables -I INPUT -p tcp --dport 443 -j ACCEPT
sudo iptables-save > /etc/iptables/rules.v4
If you're on a cloud provider:
-
AWS: Check Security Groups — inbound rule for port 443 must be
0.0.0.0/0 - Google Cloud: Check VPC Firewall Rules — allow ingress on port 443
- DigitalOcean: Check Cloud Firewall — allow TCP 443 inbound
- Hetzner: Check Firewall Rules in the Cloud Console
- Cloudflare: If proxying through Cloudflare, ensure your origin accepts connections from Cloudflare's IP ranges
If you use Cloudflare in front of your server: Shopify's edge connects to Cloudflare, which then connects to your origin. Ensure:
- Your Cloudflare SSL mode is set to Full (Strict) — not Flexible
- Your origin server has a valid certificate (not just Cloudflare's edge cert)
- The proxy URL in Partner Dashboard is your Cloudflare-proxied domain, not the origin IP
Step 4 — Diagnose DNS Resolution
If your proxy URL domain doesn't resolve correctly from Shopify's network, requests will fail before reaching your server.
Check DNS from multiple locations:
# Check from your local machine
dig yourserver.com A
nslookup yourserver.com
# Check propagation globally
# Use https://dnschecker.org and enter your domain
What to verify:
- Domain resolves to the correct IP address
- TTL is reasonable (not excessively long after a recent DNS change)
- No CNAME chain loops
- Domain is not on any DNS blocklist
Check if a recent DNS change is still propagating:
# Check against multiple DNS servers
dig @8.8.8.8 yourserver.com A # Google DNS
dig @1.1.1.1 yourserver.com A # Cloudflare DNS
dig @208.67.222.222 yourserver.com A # OpenDNS
If answers differ — DNS propagation is still in progress. Wait and retry.
Fix propagation issues:
- Reduce TTL to 300 seconds (5 minutes) at your DNS provider before making changes
- After the change is confirmed correct, raise TTL back to 3600+
Step 5 — Test the Proxy URL Directly from Shopify's Perspective
You can simulate what Shopify's edge does when it forwards a proxy request. This lets you confirm your server responds correctly before Shopify is involved.
Simulate a Shopify App Proxy request:
curl -v \
"https://yourserver.com/proxy/?shop=teststore.myshopify.com&path_prefix=%2Fapps%2Fyourproxy" \
-H "X-Forwarded-For: 23.227.38.32" \
-H "User-Agent: Shopify"
What to check in the response:
-
HTTP status code must be
200 - Response body must be valid content (JSON, HTML, or Liquid)
- Response time must be under 30 seconds (Shopify's proxy timeout)
- Content-Type header must be set correctly
If this returns 404: Your server's routing is wrong — the path isn't matching. Go to Step 6. If this times out: Your server is not responding fast enough — go to Step 7. If this works perfectly: The problem is upstream — re-examine Steps 1–4.
Step 6 — Fix Nginx Configuration for App Proxy
Once you've confirmed Shopify can reach your server, the 404 may be caused by Nginx not routing the proxied path correctly. Here is a complete, correct Nginx configuration for a Shopify App Proxy:
server {
listen 443 ssl http2;
server_name yourserver.com;
# SSL — always use fullchain
ssl_certificate /etc/letsencrypt/live/yourserver.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourserver.com/privkey.pem;
# Strong SSL settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# App Proxy endpoint
location /proxy/ {
# Pass to your application
proxy_pass http://127.0.0.1:3000/; # Your app's local port
# Required headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Preserve query parameters from Shopify
# (shop, path_prefix, signature, timestamp, etc.)
proxy_pass_request_headers on;
# Timeout — Shopify waits max 30s
proxy_connect_timeout 10s;
proxy_read_timeout 25s;
proxy_send_timeout 25s;
# Disable buffering for streaming responses
proxy_buffering off;
}
# Redirect HTTP to HTTPS
error_page 301 302 = @handle_redirect;
}
server {
listen 80;
server_name yourserver.com;
return 301 https://$host$request_uri;
}
Critical Nginx Configuration Issues That Cause 404
Issue A — Trailing slash mismatch in proxy_pass:
# ❌ Wrong — path not preserved correctly
location /proxy {
proxy_pass http://127.0.0.1:3000;
}
# ✅ Correct — trailing slashes match, path passed cleanly
location /proxy/ {
proxy_pass http://127.0.0.1:3000/;
}
When location ends with / and proxy_pass ends with /, Nginx strips the location prefix and passes only the remaining path. Mismatching these is the #1 cause of proxy 404s at the Nginx level.
Issue B — App not listening on the correct path:
# Your Nginx passes requests to http://127.0.0.1:3000/
# But your app only handles routes at /api/ not /
# Fix: Either adjust proxy_pass to include the path:
proxy_pass http://127.0.0.1:3000/api/;
# Or add the route in your application framework
Issue C — Query string parameters dropped:
Shopify App Proxy appends several query parameters to every request:
-
shop— the store domain -
path_prefix— the proxy's URL prefix -
timestamp— Unix timestamp -
signature— HMAC signature for verification
If your Nginx config strips these, your app can't verify the request:
# ✅ Explicitly preserve query string
location /proxy/ {
proxy_pass http://127.0.0.1:3000/?$query_string;
}
Issue D — Case sensitivity in location blocks:
# ❌ Case-sensitive match may miss /Proxy/ or /PROXY/
location /proxy/ { ... }
# ✅ Case-insensitive match (use sparingly)
location ~* /proxy/ { ... }
After any Nginx config change:
# Test config syntax before reloading
sudo nginx -t
# Reload without dropping connections
sudo nginx -s reload
Step 7 — Verify App Proxy Signature Validation Isn't Rejecting Requests
If your application is receiving the request but returning 404 because signature validation fails, it will look identical to a routing 404 from Nginx's perspective.
Shopify signs every App Proxy request with an HMAC using your app's client secret. Your app must verify this signature — but if the verification logic is wrong, you may be rejecting valid Shopify requests.
Correct signature verification (Node.js example):
const crypto = require('crypto');
function verifyProxySignature(query, clientSecret) {
// Step 1: Extract the signature
const { signature, ...params } = query;
if (!signature) return false;
// Step 2: Sort remaining params alphabetically and join
const message = Object.keys(params)
.sort()
.map((key) => `${key}=${params[key]}`)
.join('&');
// Step 3: Create HMAC-SHA256 using client secret
const hmac = crypto
.createHmac('sha256', clientSecret)
.update(message)
.digest('hex');
// Step 4: Compare using timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(hmac),
Buffer.from(signature)
);
}
// In your route handler
app.get('/proxy/', (req, res) => {
const isValid = verifyProxySignature(req.query, process.env.SHOPIFY_CLIENT_SECRET);
if (!isValid) {
// Log this — don't just return 404 silently
console.error('Proxy signature verification failed', req.query);
return res.status(401).json({ error: 'Unauthorized' });
}
// Handle valid proxy request
res.json({ status: 'ok' });
});
Common signature verification mistakes:
// ❌ Wrong — includes 'signature' in the message
const message = Object.keys(query)
.sort()
.map(key => `${key}=${query[key]}`)
.join('&');
// Must exclude 'signature' itself from the message
// ❌ Wrong — using == instead of timing-safe comparison
if (hmac == signature) { ... }
// Vulnerable to timing attacks and may behave unexpectedly
// ❌ Wrong — using client ID instead of client secret
const hmac = crypto.createHmac('sha256', CLIENT_ID)...
// Must use CLIENT_SECRET
Debug tip — log before rejecting:
// Temporarily log the computed vs received signature
console.log('Computed:', hmac);
console.log('Received:', signature);
console.log('Params used:', message);
Never deploy signature logging to production — it exposes sensitive data.
Step 8 — Check the App Proxy Response Format
Shopify has specific requirements for what your proxy endpoint returns. Returning the wrong content type or format causes Shopify's edge to generate its own 404.
Requirements:
| Requirement | Details |
|---|---|
| Content-Type | Must be application/json, application/liquid, or text/html |
| Liquid responses | Must be valid Liquid — Shopify renders it server-side |
| Status code | Must return 200 — Shopify does not forward non-200 responses transparently |
| Response size | Must be under 5MB |
| Response time | Must complete within 30 seconds |
Correct response examples:
// JSON response
res.setHeader('Content-Type', 'application/json');
res.status(200).json({ products: [...] });
// HTML response
res.setHeader('Content-Type', 'text/html');
res.status(200).send('<div>Hello from proxy</div>');
// Liquid response (rendered by Shopify)
res.setHeader('Content-Type', 'application/liquid');
res.status(200).send('{% assign title = "Hello" %}{{ title }}');
What causes Shopify to show 404 instead of your response:
// ❌ Wrong status code
res.status(404).json({ error: 'Not found' });
// Shopify converts this to a store 404 page
// ❌ Missing Content-Type
res.send('some data');
// Shopify may not know how to handle the response
// ❌ Redirect response
res.redirect('https://othersite.com');
// Shopify does not follow proxy redirects
Step 9 — Enable Detailed Proxy Debugging
When you can't tell where the failure is happening, add logging at every layer.
Nginx access log — confirm requests are arriving:
# In your server block
access_log /var/log/nginx/proxy_access.log combined;
error_log /var/log/nginx/proxy_error.log warn;
# Watch in real time
tail -f /var/log/nginx/proxy_access.log
# Then trigger the proxy and watch for entries
If no entry appears when you access the proxy URL: → Request never reached Nginx → Problem is in Steps 1–4
If an entry appears with 404: → Nginx received the request but routed it incorrectly → Problem is in Step 6
Application-level logging:
// Log every incoming request to your proxy endpoint
app.use('/proxy', (req, res, next) => {
console.log(`[PROXY] ${req.method} ${req.url}`);
console.log(`[PROXY] Query:`, req.query);
console.log(`[PROXY] Headers:`, req.headers);
next();
});
Test with Shopify's proxy debug parameter:
When testing in development, you can use
ngrok http 3000
# Copy the https:// URL ngrok provides
# Update Proxy URL in Partner Dashboard to: https://abc123.ngrok.io/proxy/
# Test — ngrok's dashboard shows every request with full headers and body
Ngrok's web interface at http://127.0.0.1:4040 is invaluable — it shows you exactly what Shopify sent, byte by byte.
Complete Diagnostic Flowchart
App Proxy returns 404
↓
Does Nginx show ANY incoming traffic?
↓
NO ──────────────────────────────────────────┐
↓ ↓
Check Partner Dashboard config (Step 1) You're here
Check TLS certificate (Step 2)
Check firewall rules (Step 3)
Check DNS resolution (Step 4)
↓
YES
↓
Is the request reaching your application?
↓
NO → Fix Nginx routing config (Step 6)
↓
YES
↓
Is signature verification passing?
↓
NO → Fix HMAC verification logic (Step 7)
↓
YES
↓
Is the response format correct?
↓
NO → Fix Content-Type and status code (Step 8)
↓
YES → Contact Shopify Partner Support with full logs
Quick Reference: Most Common Causes by Symptom
| Symptom | Most Likely Cause | Fix |
|---|---|---|
| No Nginx traffic, 404 in browser | Invalid SSL cert | Replace with valid CA cert + fullchain.pem |
| No Nginx traffic, 404 in browser | Firewall blocking port 443 | Open inbound 443 to all IPs |
| No Nginx traffic, 404 in browser | Wrong Proxy URL in dashboard | Correct URL, ensure HTTPS |
| No Nginx traffic, 404 in browser | DNS not resolving | Fix DNS, wait for propagation |
| Nginx gets traffic, 404 returned | Trailing slash mismatch | Match slashes in location and proxy_pass |
| Nginx gets traffic, 404 returned | App not handling the path | Add correct route in application |
| App gets request, returns 401/404 | Signature verification failing | Fix HMAC logic, use client secret |
| Response received but 404 shown | Wrong status code returned | Return HTTP 200 always |
| Intermittent 404 | Timeout exceeded | Optimize response time under 25s |