> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instapods.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting

> Solutions to common InstaPods issues.

## Deploy Doctor

When a pod is running but your app isn't answering - visitors get a 502 - or when a deploy fails,
InstaPods diagnoses it for you instead of leaving you a stack trace. You don't turn this on; it runs
by itself and writes what it found where you'll see it.

**Where the verdict appears:**

| Situation | Where you see it |
| - | - |
| The pod is up but your app isn't reachable | An amber banner at the top of the pod's detail page |
| A deploy failed | A banner on the deployment in the pod's **Git** tab, above the build log |

Each verdict has the same three parts: what's wrong, the evidence that proves it (the actual error
line from your app's log, the port it's really listening on), and the fix. Where InstaPods can apply
the fix itself, the banner also shows an **Apply fix** button.

<Note>
  If nothing matches a known signature, you get an honest "we couldn't pin this down" plus the first
  error from your log - not a guess. It never asserts a cause it did not measure. Send us the pod
  name and we'll look with you.
</Note>

### What it recognises on a running pod

| Diagnosis | What it means | One-click fix |
| - | - | - |
| **Wrong port** | Your app is listening, but on a different port than the one we forward traffic to | Yes - re-points the proxy at the port your app is actually on |
| **Bound to 127.0.0.1** | Your app listens on loopback only, so the proxy can't reach it | No - the bind address is in your own code. Bind `0.0.0.0` instead |
| **Missing dependency** | The app exits importing a package that wasn't installed | Yes - re-runs the dependency install and restarts |
| **Wrong entrypoint** | The start command points at a file that isn't in the pod | Yes, when we can see the real entrypoint - rewrites the start command to it |
| **Node version mismatch** | `package.json` `engines.node` requires a newer Node than the pod runs | Yes - switches the pod to the smallest supported Node major that satisfies it |
| **Missing database** | The app crash-loops connecting to a database port that nothing is listening on | No - you choose between a [managed service](/services/overview) and your own external database |
| **Not a web app** | It's a bot or worker: it opens an outbound connection and never serves HTTP | Yes - converts it to a [worker pod](/guides/worker-pods), which removes the public URL and stops the 502 |
| **Crash loop** | The app dies on every start. The banner quotes the actual error it died with | No - it's your code. Fix the error and redeploy |
| **App error** | The app is up and answering, but with a `500` of its own on every request | No - the failure is inside your app. The banner quotes the error from its log |

### Applying a fix

Click **Apply fix** in the banner. Only the pod's owner can do this, and nothing is applied without
that click - the diagnosis alone never changes your pod.

<Tabs>
  <Tab title="Dashboard">
    Open the pod, read the banner, and click **Apply fix**. The banner then tells you what was done.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    curl -X POST https://app.instapods.com/api/pods/my-app/apply-fix \
      -H "Authorization: Bearer YOUR_TOKEN"
    ```

    Returns `{"status": "applied", "message": "..."}`. It returns `400` when the current diagnosis
    has no automatic fix (or when there's no diagnosis at all) - the banner's manual steps are the
    answer in that case.
  </Tab>
</Tabs>

Re-installing dependencies and switching Node run in the background and take a couple of minutes;
re-pointing the proxy and rewriting the start command are immediate. After any of them the health
verdict is cleared and your pod is re-checked from scratch, so the banner clears on its own once the
app comes up. Every applied fix is recorded in the pod's **Events** tab.

### When a deploy fails

Failed deploys get their own diagnosis, on the deployment itself. It knows which step died - clone,
install, build, release commands, or the app failing to stay up afterwards - so the advice matches
the stage. Common verdicts include a stale or missing lockfile, a build that needs environment
variables you haven't set, a monorepo sibling package failing a build nothing serves, a dev/watch
script being used to serve production, a branch that isn't on the remote, and repo authentication.

Three are worth knowing before you hit them:

* **"GitHub is rate-limiting us, not blocking you."** A clone of a public repository can fail
  looking exactly like an access problem. GitHub throttles unauthenticated traffic per IP address,
  and a busy pod host hits that limit - nothing is wrong with your repository or your account. The
  deploy already retried on its own before reporting this, so deploy again in a few minutes and it
  usually goes through. To stop hitting it at all, connect GitHub from the pod's **Git** tab: an
  authenticated clone isn't subject to the limit.
* **The migration wants to create a role or a database.** A release command that runs
  `CREATE ROLE`, `CREATE DATABASE` or `CREATE EXTENSION` fails on a
  [managed service](/services/overview): your database user owns its own database and nothing above
  it. Point the migration at the database you already have, or drop the step.
* **The start command points at a file the deploy removed.** When a deploy fails partway, the pod
  can be left starting a file that is no longer there. The verdict says so, and names the entrypoint
  it found in your `package.json` instead of asking you which one is right.

Deploy-failure verdicts are diagnosis only - there's no **Apply fix** button on a deployment.

### "Ran out of memory on the Launch plan"

A build killed for memory says so in plain words instead of reporting exit code 137. During install
and build a pod is temporarily given more memory than its plan, and released back before your app
starts, so most builds fit on Launch - but a heavy production build (a large Next.js app, a Go link
step) can still exceed it.

**Nothing is ever upgraded automatically.** If you hit this, deploy again on the Build plan
(2GB, \$7/mo) or larger.

### "Your app isn't responding" emails

InstaPods checks that your pod actually answers on its public URL. When it stops answering, we email
you - you do not have to be watching the dashboard to find out your site is down.

**An app that answers every request with a `500` counts as down.** A pod serving nothing but its own
server error is broken from a visitor's point of view, so it is treated that way: you get the same
email, and the Deploy Doctor verdict on it is **App error** rather than a guess about ports.

**When the cause is memory, the alert says so.** If your pod spent most of the last five minutes
stalled reclaiming memory, the banner and the email say "your app is out of memory" rather than
reporting it as an app that stopped answering. The fix is a larger plan, or holding less in memory.
Where the stall is ours rather than yours, the message says that too, and it is already flagged on
our side.

This covers pods running your own code on a preset. **1-Click App pods and AI Builder pods are not
URL-probed**, so they do not produce these alerts - a 1-Click App manages its own health, and a
builder pod is expected to churn while you work on it.

* **At most one email per pod per day** while it stays down, not one per check. A pod that flaps up
  and down will not re-send inside that window either.
* The email carries the [Deploy Doctor](#deploy-doctor) verdict, not just "your pod is down" - the
  same diagnosis and suggested fix you would see on the pod's banner, including the failing line from
  your app's log when it is crash-looping.
* It stops on its own once the pod answers again. There is nothing to acknowledge or clear.

If you are getting these for a pod you have deliberately taken out of service, either stop the pod
or convert it to a [worker pod](/guides/worker-pods) - a worker has no public URL, so it is not
checked for one.

## SSH Issues

### "REMOTE HOST IDENTIFICATION HAS CHANGED"

This happens when a new pod reuses an SSH port previously used by a deleted pod.

**Fix**: Remove the stale host key:

```bash theme={null}
ssh-keygen -R "[nbg1-1.instapods.app]:PORT"
```

Then reconnect. The CLI handles this automatically when you delete a pod via `instapods pods delete`.

### "Permission denied (publickey)"

Your SSH key isn't authorized on the pod.

**Fix**: Add your key:

```bash theme={null}
instapods ssh-keys add my-app
```

Or add it via the dashboard's SSH tab.

### SSH connection timeout

The pod may be stopped, or the SSH port might be blocked.

**Fix**:

1. Check pod status: `instapods pods get my-app`
2. If stopped, start it: `instapods pods start my-app`
3. Verify you're using the correct port

## Pod Issues

### Pod stuck in "creating" state

Rarely, a pod creation can get stuck if the underlying container fails to start.

**Fix**: Delete the pod and create a new one:

```bash theme={null}
instapods pods delete my-app -f
instapods pods create my-app --preset nodejs
```

### Application not responding on the public URL

Open the pod's detail page first - [Deploy Doctor](#deploy-doctor) usually already names the cause,
and often offers a one-click fix. The rest of this section is the manual check.

Make sure your application is listening on the correct port:

| Preset | Required Port |
| - | - |
| Static | 80 (handled by nginx) |
| PHP | 80 (handled by nginx + PHP-FPM) |
| Node.js | **3000** |
| Python | **8000** |
| Go | **8080** |

For Node.js and Python, your app must bind to `0.0.0.0` (not `127.0.0.1` or `localhost`):

```javascript theme={null}
// Node.js — correct
app.listen(3000, '0.0.0.0');

// Node.js — incorrect (won't be accessible)
app.listen(3000, 'localhost');
```

```python theme={null}
# Python — correct
app.run(host='0.0.0.0', port=8000)

# Python — incorrect
app.run(host='127.0.0.1', port=8000)
```

### Pod shows "suspended" status

Your team's subscription is suspended due to unpaid invoices.

**Fix**: Go to **Billing** in the dashboard and pay the outstanding invoice. After payment, manually start your pods.

## Service Issues

### Service stuck in "installing"

Service installation runs in the background and typically takes 8–15 seconds. If it's been more than a minute:

1. Check the service status: `instapods services list my-app`
2. If it shows "error", the installation failed — check the error message
3. Try removing and reinstalling:
   ```bash theme={null}
   # Via API
   curl -X DELETE https://app.instapods.com/api/pods/my-app/services/mysql \
     -H "Authorization: Bearer YOUR_TOKEN"

   instapods services add my-app -s mysql -w
   ```

### "Plan does not allow services"

You're on the Launch plan, which doesn't support database services.

**Fix**: Upgrade to the Build plan or higher from the billing page.

## File Issues

### Files not appearing after upload

If you uploaded files via SCP or the CLI but they don't appear in the dashboard file browser:

1. Verify the file path — the file browser starts from the pod's app root (`/home/instapod/app`)
2. Check file permissions: `instapods exec my-app -- ls -la /home/instapod/app/`
3. Make sure you uploaded to the correct directory (PHP uses `/home/instapod/app/public` for web files)

### "Permission denied" when writing files

Files in the app directory should be owned by the `instapod` user. If they're owned by `root`:

```bash theme={null}
instapods exec my-app -- sudo chown -R instapod:instapod /home/instapod/app
```

### A file on my site returns 403

Every pod's public URL refuses paths containing a dot-file segment - `/.env`, `/.env.local`,
`/.git/config`, `/.npmrc` - so that a pod can't hand out its own secrets. The file is still there
and your app can still read it; only the direct web request is blocked.

`.well-known/` is exempt, so certificate renewal and `security.txt` are unaffected. If you
genuinely need to serve a path that begins with a dot, rename it - there is no per-pod opt-out.

A `403` on a path that is *not* a dot-file is coming from your own app or its config, not from
InstaPods.

## Domain Issues

### Domain verification failing

1. Check that the DNS record has propagated: `dig CNAME app.example.com` or `dig TXT _instapods.app.example.com`
2. Ensure the record matches exactly what InstaPods expects (shown on the domains page)
3. Wait a few minutes and try again — DNS propagation can take time

### SSL certificate not provisioning

After domain verification, SSL provisioning runs via certbot. If it fails:

1. Ensure the domain points to the correct server IP
2. Check that ports 80 and 443 are accessible
3. Try removing and re-adding the domain

## Getting Help

If your issue isn't covered here:

* Check the pod's **Events** tab for error details
* Check pod **Logs** for application-level errors
* Contact support through the dashboard


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.