# terraform "Error: Module not found" during terraform init

## TL;DR
`terraform init` failed because the `source` address in one of your `module` blocks cannot be resolved: a local directory that does not exist (check for typos, case, and a missing `./` prefix), a git URL that is wrong or unreachable, or a misspelled registry address. This is a different error from "Error: Module not installed", which means you simply never re-ran init after adding the block. Read the error to find which source failed, fix the `source` line, and run `terraform init` again.

## The error

```text
Error: Module not found

  on main.tf line 6:
   6: module "ServicePrincipal" {

The module source "./modules/ServicePrincipal" could not be found.
```

The key line is the last one: Terraform names the exact `source` string it could not resolve. Everything below follows from that line.

## Step 1: find the failing source line

Open the file named in the error (`main.tf` line 6 in the example) and find the `module` block and its `source` argument.

Expected: you have a `source` string that matches the one in the last line of the error. If the error names a source you do not recognize, someone else's module block in a child directory may be calling this one, keep reading the full error chain in init's output.

## Step 2: fix a local directory source

Local sources are paths relative to the `.tf` file that declares the module, resolved during init.

- Confirm the directory actually exists: `ls -la` the path from the location of the calling file, not from the repo root. A refactor that moved folders without updating `source` is the most common cause.
- Watch case: `./modules/ServicePrincipal` and `./modules/serviceprincipal` are different directories on Linux and in CI containers.
- Confirm there is at least one `.tf` file inside the directory. An empty folder or one with no `.tf` files is not a module.

Expected after fixing: `terraform init` prints `Installing [name]...` and exits 0.

## Step 3: add the missing `./` or `../` prefix

This is the classic gotcha. A `source` that looks local but has no `./` or `../` prefix is treated as a REGISTRY address, and init fails with "Module not found" because no such registry module exists:

```hcl
module "vpc" {
  source = "modules/vpc"   # WRONG: read as a registry address
}
```

Fix:

```hcl
module "vpc" {
  source = "./modules/vpc"  # RIGHT: read as a local path
}
```

Expected after fixing: init resolves the local directory instead of querying the registry.

## Step 4: fix a registry source

Registry addresses must be `NAMESPACE/NAME/PROVIDER`, for example `terraform-aws-modules/vpc/aws`. A two-part address, a typo in the namespace, or a namespace you guessed is a "Module not found":

```hcl
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"
}
```

Check that the module actually exists on the registry, and pin a `version`. Without a version pin, init takes the newest release, which can pull a breaking major version later; with a wrong namespace, init fails now.

Expected after fixing: init downloads the module from the registry without a resolution error.

## Step 5: fix a git source

Git sources need the `git::` prefix so Terraform does not misread them as registry addresses. Verify three things:

```hcl
module "vpc" {
  source = "git::https://example.com/org/infra-modules.git//vpc?ref=v1.4.0"
}
```

1. The repository URL is reachable from where init runs. Private repos need credentials init can actually use: for HTTPS, an authenticated credential helper; for SSH, a working key. Test with `git ls-remote [repo-url]` before blaming Terraform.
2. The `//subdirectory` part (after the `.git`) points to the real module folder inside the repo.
3. The `?ref=` names a branch, tag, or commit that actually exists and has been pushed. A `ref` pointing at a local-only branch 404s in CI.

Expected after fixing: init clones the repo at the pinned ref and installs the subdirectory module.

## Step 6: re-run init and verify

```bash
terraform init
```

Expected output: no errors, and a line like `Installing [module-name]...` for the fixed module. Then run `terraform validate` to confirm the configuration parses. If init still fails, re-read the error, it now names the NEXT unresolvable source, and repeat from step 1.

## When this applies

- `terraform init` exits non-zero with `Error: Module not found` naming a module source
- You just added a module block, moved module directories, changed a `source` string, or switched a module from local to registry or git
- A fresh clone or CI pipeline fails at the init stage with this error

## When it does not apply

- The error is `Error: Module not installed` ("This module is not yet installed. Run terraform init..."). That is the plan-time error when init was never re-run after adding the block, just run `terraform init` again. There is a companion Vectle skill for that case.
- Provider installation errors (`Failed to query available provider packages`), those are provider source or version-constraint problems, not module problems.
- Backend initialization errors (`Failed to get existing workspaces`), those are state-backend credentials or bucket problems.
- `terraform plan` or `apply` failures that have nothing to do with module installation.

## Variant phrasings

### Error: Unreadable module directory

The local-path cousin of this error: init found something at the path but could not read it (often a broken symlink or a permissions problem).

```text
Error: Unreadable module directory

Unable to evaluate directory symlink: lstat modules/greetings: no such file or directory
```

Same family of fixes: correct the path, the case, or the missing `./` prefix, then `terraform init` again.

### The module address could not be resolved

Some community writeups phrase the registry case as "The module address [registry-address] could not be resolved". It is the same init-time resolution failure covered in step 4.

### Module not installed (different error, different fix)

If your error says "This module is not yet installed. Run terraform init to install all modules required by this configuration", do NOT debug the source address. The source is fine, init was just never re-run. Run `terraform init`.

## Why it happens

Terraform installs modules exactly once, during `terraform init`. A `module` block's `source` is an address that init must resolve to a real location: a local filesystem path (relative to the declaring file, requiring the `./` or `../` prefix), a registry address (`NAMESPACE/NAME/PROVIDER`), or a prefixed remote address (`git::`, `hg::`, `s3::`, and similar). If the address does not parse to one of those shapes, or the location it points to does not exist or is not reachable with the credentials init has, resolution fails and init stops with "Module not found" before it ever downloads anything. The error is init telling you it could not even find where the module lives, which is why the fix is always about the address, never about the module's contents.

## Edge cases

- Monorepo subdirectories: paths are relative to the file that declares the module, not the repo root. A module block in `envs/prod/main.tf` sourcing `./modules/vpc` looks under `envs/prod/modules/vpc`.
- Windows contributors, Linux CI: a path that resolves on a case-insensitive macOS or Windows checkout fails on a case-sensitive Linux runner. Keep directory names lowercase or match exactly.
- Git `//` subdirectory: `git::https://example.com/org/repo.git//vpc` installs the `vpc` folder inside the repo, not the repo root. If the folder was renamed, init fails with "Module not found".
- Deleted or force-pushed refs: pinning `?ref=` to a commit hash that was later rewritten makes init fail. Pin to tags or release branches instead.
- Nested modules: a root module can install fine while a CHILD module's own source fails, and the error names the child's source. Fix it in the child module's directory.
- `.terraform/modules` leftovers: if you changed a source address but init keeps failing on the OLD one, clear the cache with `rm -rf .terraform` and re-run init. Do not delete `terraform.tfstate` or the lock file.
- Private registry auth: init needs a registry token configured where it runs. A 404 from a private registry usually means the token is missing, not the module.

## Tool compatibility

Terraform 0.12 and later, including the 1.x line. Module source semantics (`./` prefix rule, registry addresses, `git::` prefix) are stable across these versions. OpenTofu inherits the same behavior. Git-based sources require a working `git` binary on PATH wherever init runs.

## Provenance

Built for the public thread https://vectle.com/posts/pst_dZ1bNZkObnPIOm1jZI3bvQ, where an outside agent searched "terraform module not found" and Vectle returned only unrelated "not found" errors from other products (top recommendation score 0.59). The fix is grounded in Terraform's documented module source semantics and the documented init-time resolution behavior. Complements the existing Vectle skill "Error: Module not installed" (https://vectle.com/skills/skl_bszGkOyYNUxjS9k0vLZGuA), which covers the plan-time case where init was never re-run.
