This is part one of three. It covers everything you need to do real work with HashiCorp Vault, not a teaser. By the end you can start a server, store and version secrets, write a policy that gives a person or an application exactly the access it needs, log in as that person, hand out short-lived tokens, and read the error messages Vault gives you when something is wrong. Mid-level and Senior take the same topics further; nothing here is thrown away.
The guide targets Vault 2.1.1, released on 16 September 2026. The 2.0 release in April 2026 was the first major version since 1.0, and it changed a few details that older blog posts and tutorials still get wrong. Where that matters, the text says so and shows the current form.
Each section ends with a Try it task. Do them as you go. They take a few minutes each, and Vault only makes sense once you have watched a request get refused with "permission denied" and then fixed it yourself.
What Vault is, and the problem it solves
Vault is a server that stores, generates and hands out secrets, and decides who is allowed to see each one. A secret is anything that grants access or must stay private: a database password, a cloud access key, an API token for a payment provider, a TLS private key, an encryption key. Vault puts all of them behind one HTTP API, requires every caller to prove who they are, checks a written policy on every request, and records who asked for what.
To see why that matters, look at what teams did before.
The first habit is secrets in source code. A developer pastes a database password into a config file, commits it, and pushes. The password is now in every clone and in the history forever. Deleting the line in a later commit removes nothing, because Git remembers. Automated scanners crawl public repositories for exactly these strings within minutes of a push.
The second habit is secrets in environment variables and CI settings. That is better than source code, but the values are still long-lived, shared by everyone who can edit the pipeline, and rarely rotated. When an engineer leaves, nobody knows which of forty variables they had seen. When something leaks, nobody can tell which value leaked, from where, or when.
The third habit is a shared spreadsheet or chat message, which needs no further comment.
Underneath these habits sit four problems that Vault is designed to address together:
- Sprawl. Secrets live in dozens of places, so nobody can list them or change them in one go.
- Long life. A password created in 2021 and never changed is a standing invitation. The longer a secret lives, the more people have touched it and the more places it has been copied.
- No audit trail. When a secret is read from a file, nothing records the read. After an incident you cannot answer "who had access?".
- No fine-grained control. Anyone who can read the file can read every secret in it.
Vault's answer is to centralise secrets, put identity and policy in front of every read, record every request in an audit log, and, where it can, stop storing secrets at all. That last idea is the most interesting one. For many systems, Vault can create a credential on demand, for example a fresh database user that expires in an hour, so there is no long-lived password to leak. Later guides in this series cover that in depth; this guide concentrates on the foundations.
That diagram is the whole product. A client logs in through an auth method and receives a token. Policies attached to the token decide which paths the token may touch. Each path leads to a secrets engine, which does the real work. Everything Vault keeps on disk is encrypted. The rest of this guide unpacks each box.
A note on licensing, because you will meet the question at work. Since Vault 1.15 (August 2023) the source code is under the Business Source License 1.1 rather than the older MPL 2.0 license. The free edition you download is called Vault Community Edition, and that is what this guide uses. There is also OpenBao, a separate community fork of the last MPL-licensed code, governed by the Linux Foundation. It is a different product: do not assume its commands and features match Vault's. Finally, HashiCorp is now part of IBM, and you will see the product marketed as "IBM Vault". The command is still vault and the documentation still lives at developer.hashicorp.com/vault.
- Pick one project you know. List every secret it needs to run: database passwords, API keys, signing keys.
- For each one, write down where it lives today, who can read it, and when it was last changed.
- Mark every secret that has no answer for one of those three questions.
The mental model: paths, tokens, policies and the seal
Vault has a small vocabulary. Learn these nouns once and every later feature is a variation on them.
Everything is a path
Vault is an HTTP API server, and every feature is reached at a URL of the form /v1/<path>. The CLI is a convenience wrapper that builds those requests for you. A secret stored at secret/myapp/config is really an HTTP request to /v1/secret/data/myapp/config (we will explain the extra data later). Creating a database user is a request to a path under database/. Even Vault's own administration is a path: sys/ is the system backend, and it is where you turn features on, read the health of the server and manage policies.
This design has a useful consequence. Because every action is a read or a write to a path, access control is also about paths. A policy does not say "this person is a database admin". It says "this token may read the path database/creds/app". You will find that once you think in paths, permissions become concrete and testable.
Secrets engines: what lives behind a path
A secrets engine is a component mounted at a path prefix that stores, generates or encrypts data. Mounting an engine at secret/ means every request under secret/ is handed to that engine. You can mount several engines, and you can mount the same kind of engine at different prefixes.
Engines fall into three families:
- Static secrets. You put a value in, you get the same value out. The KV (key-value) engine is the main example and is what you will use first.
- Dynamic secrets. Vault creates a credential when asked and deletes it when its lease ends. The database, AWS, GCP, Azure, Kubernetes, SSH and PKI (certificates) engines work this way.
- Encryption as a service. The Transit engine encrypts and decrypts data for your application without ever giving the application the key.
Auth methods: how callers prove who they are
An auth method verifies an identity and maps it to a set of policies. Auth methods always live under auth/, for example auth/userpass/. A person might log in with a username and password, or through a company single sign-on system (OIDC, LDAP). An application might log in with approle, or with its Kubernetes service account, or with a cloud identity. The method you pick depends on who the caller is, and each method ends the same way: Vault returns a token.
Tokens: the credential you actually carry
The token is the core credential. Every request to Vault, except a few unauthenticated system endpoints, carries a token in the X-Vault-Token header (or as Authorization: Bearer <token>). When you log in, Vault gives you a token; when you use the CLI, it stores that token in a file, ~/.vault-token, and sends it for you.
Tokens you will see begin with a prefix that tells you the type: hvs. for a service token, hvb. for a batch token and hvr. for a recovery token. Before Vault 1.10 the prefixes were s., b. and r., so an old tutorial showing s.abc123 is not wrong, just old. Service tokens are the ordinary kind: stored by Vault, renewable, revocable. Batch tokens are lightweight and cannot be renewed or revoked, which suits very high-volume short jobs.
Every token has a TTL (time to live). When it expires, it stops working. The system default and the system maximum are both 768 hours, which is 32 days; you can tighten them per mount and per role. Tokens form a hierarchy: a token is a child of the token that created it, and revoking a parent revokes all of its children. A token with no parent is an orphan; tokens issued by logging in through an auth method are orphans.
A token also has an accessor, a reference you can use to look up, renew or revoke the token or check its capabilities without knowing the token itself. The accessor cannot be used to authenticate, so it is safe to log.
Policies: what a token may do
A policy is a small document, written in HCL (HashiCorp Configuration Language) or JSON, that lists paths and the capabilities allowed on each. The capabilities are create, read, update, patch, delete, list, sudo and deny. Policies are deny by default: a path not mentioned is forbidden. If two policies disagree, deny always wins.
Two policies exist from the start. default is attached to every token unless you opt out; it grants a few harmless things such as looking up your own token. You can edit it, but not delete it. root is immutable and grants everything.
The root token, and why you should not keep it
The root token carries the root policy and can do anything. You get one at initialisation and you should treat it like the master key to a building: use it once to set things up, then revoke it. Day-to-day work uses tokens with narrow policies. From Vault 2.0 onwards, even generating a replacement root token with vault operator generate-root requires an existing token as well as key shares, which is one more reason to plan your administrative accounts rather than lean on root.
Sealed and unsealed
This is the concept newcomers find strangest. When a Vault server starts, it can reach its storage but cannot read it, because the data is encrypted and the key is not in memory. The server is sealed. Until someone unseals it, it answers almost nothing, and every data request returns an error that says Vault is sealed.
Why design it this way? Because the storage (a disk, or a cloud volume) is treated as untrusted. If someone steals the disk, or a backup, they get ciphertext. The encryption happens in a layer Vault calls the barrier, which uses AES-256-GCM. The key hierarchy has three levels:
- Your data is encrypted with an encryption key (the keyring).
- The keyring is encrypted with the root key.
- The root key is protected by the unseal key, which by default is split into pieces using Shamir's Secret Sharing.
With the default setup, vault operator init creates five key shares and requires any three of them to unseal. You give the shares to different people, so no single person can unseal the server alone. Production systems usually avoid the manual step with auto-unseal, where a cloud key management service (AWS KMS, Azure Key Vault, GCP Cloud KMS and others) protects the root key. In that mode init issues recovery keys instead, which cannot decrypt anything and only authorise sensitive operations.
Leases, mounts and audit
Three more nouns complete the vocabulary. A lease is metadata attached to a dynamic secret (and to service tokens): a lease ID, a TTL and a renewable flag. When the lease ends, Vault revokes the credential. You can revoke leases one at a time or by prefix. A mount is simply an engine or auth method attached to a path; you "enable" one to mount it. An audit device is a log sink (a file, syslog or a socket) that records every request and response, with sensitive values hashed so the log does not itself leak secrets.
- Without looking back, draw the diagram with four boxes: client, auth method, token with policy, secrets engine.
- Label where "sealed" applies (it is about the server as a whole, not any one box).
- Explain in one sentence why the root token should be revoked after setup.
Installing Vault and checking the setup
Install the single vault binary. The same file is both the server and the command line client.
On macOS, use the HashiCorp tap. The formula in Homebrew's core repository stopped receiving updates after the license change, so a plain brew install vault can give you something stale or nothing at all.
brew tap hashicorp/tap
brew install hashicorp/tap/vault
vault version
On Ubuntu or Debian, add HashiCorp's signed package repository and install:
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vault
On Red Hat family systems the package repository is added with yum-config-manager (RHEL, CentOS, Amazon Linux) or fetched with wget (Fedora), and then installed with yum or dnf; the exact lines are on the official install page at developer.hashicorp.com/vault/install. On Windows, download the zip from releases.hashicorp.com, place vault.exe in a folder on your PATH, and note that Chocolatey and winget packages exist but are maintained by the community, not by HashiCorp. You can also download the zip for any platform from releases.hashicorp.com/vault/2.1.1/ and verify it against the published SHA256 checksums.
If you prefer not to install anything, Docker works for a disposable server:
docker run --rm -p 8200:8200 -e VAULT_DEV_ROOT_TOKEN_ID=root hashicorp/vault:2.1.1 server -dev -dev-listen-address=0.0.0.0:8200
Since Vault 2.0 the official container runs as the vault user, and since 2.0.2 the binary no longer has the cap_ipc_lock capability, so a container cannot lock memory. That matters for real deployments, where you set disable_mlock = true; a throwaway dev container does not care.
Verify the install:
vault version
Vault v2.1.1 (abcdef123456), built 2026-09-16T10:00:00Z
The commit hash and build date on your machine will differ. What matters is the version number. Two optional extras are worth doing now. vault -h lists every command, and vault -autocomplete-install adds tab completion to your shell (restart the shell afterwards).
-output-curl-string to almost any CLI command and Vault prints the equivalent curl command instead of running it. It is the fastest way to learn the HTTP API, and we will use it later in this guide.
- Install Vault with the method for your system.
- Run
vault versionand confirm it says 2.1.1, or the newest release you can get. - Run
vault -hand find three command names you have not heard of. Do not read about them yet.
Starting a development server
Vault has a built-in development mode that gives you a working server in one command. It exists for learning and testing. It is not safe for real use, and the next paragraph explains why.
vault server -dev
In dev mode Vault stores everything in memory, so all data vanishes when you stop it. It starts already unsealed with a single unseal key. It listens on plain HTTP at 127.0.0.1:8200, with no TLS. It enables a KV version 2 engine at secret/. And it prints a root token in the terminal. Each of those conveniences would be a serious flaw in production, which is why the mode has "dev" in the name.
Leave that terminal running. The output includes lines like these:
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.
You may need to set the following environment variables:
$ export VAULT_ADDR='http://127.0.0.1:8200'
The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.
Root Token: hvs.xxxxxxxxxxxxxxxxxxxxxxxx
Open a second terminal for the client. The CLI needs two environment variables. VAULT_ADDR tells it where the server is, and this one is required because the CLI defaults to https://127.0.0.1:8200, while the dev server speaks plain HTTP. VAULT_TOKEN supplies the token:
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='hvs.xxxxxxxxxxxxxxxxxxxxxxxx'
vault status
Paste the real root token printed by your server. The status output looks like this:
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 2.1.1
Storage Type inmem
Cluster Name vault-cluster-...
HA Enabled false
Read it line by line. Initialized true means the storage has been set up. Sealed false means the server can read its data. Total Shares 1 and Threshold 1 show the dev-mode shortcut of a single unseal key. Storage Type inmem confirms the memory-only storage. HA Enabled false means this is a single node rather than a cluster.
If you prefer a fixed token so that you do not have to copy a new one on every start, pass it yourself:
vault server -dev -dev-root-token-id=root
Now the token is simply root. Convenient on a laptop, and exactly what you must never do anywhere else.
The other path is the web interface. Open http://127.0.0.1:8200 in a browser and sign in with the token. The UI is a pleasant way to browse, but the CLI is what you will use in scripts and what interviewers ask about, so this guide shows the CLI.
VAULT_ADDR you will see an error like http: server gave HTTP response to HTTPS client, or a warning that the address is unset and defaulting to https://127.0.0.1:8200. The client is speaking TLS to a server that does not. Export VAULT_ADDR with http:// and retry.
- Start
vault server -dev -dev-root-token-id=rootin one terminal. - In another, export
VAULT_ADDRandVAULT_TOKEN=root, then runvault status. - Stop the server with Ctrl+C, start it again, and run
vault statusonce more.
Your first secrets: the KV engine
The development server already has a KV (key-value) engine mounted at secret/. KV is the simplest engine and the one you will use most in the beginning. It stores named sets of key-value pairs at paths you choose.
There are two versions. KV version 1 stores one value per path with no history. KV version 2 keeps versions of each secret, lets you delete and recover them, and stores metadata. Version 2 is what the dev server mounts and what you should use by default. One detail trips up almost everyone, so here it is early: in version 2 the HTTP API inserts data/ (and metadata/) into the path. The CLI hides that for you, the policies you write do not.
Check what is mounted:
vault secrets list
Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_a1b2c3d4 per-token private secret storage
identity/ identity identity_e5f6a7b8 identity store
secret/ kv kv_c9d0e1f2 key/value secret storage
sys/ system system_03a4b5c6 system endpoints used for control, policy and debugging
Write your first secret. The -mount flag names the engine, and the last argument is the path inside it:
vault kv put -mount=secret myapp/config username=app password=s3cr3t
======= Secret Path =======
secret/data/myapp/config
======= Metadata =======
Key Value
--- -----
created_time 2026-10-01T09:30:12.123456Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
The printed Secret Path is secret/data/myapp/config, which shows the data/ segment that the CLI added. Note version 1. Read it back:
vault kv get -mount=secret myapp/config
======== Secret Path ========
secret/data/myapp/config
======= Metadata =======
Key Value
--- -----
created_time 2026-10-01T09:30:12.123456Z
version 1
====== Data ======
Key Value
--- -----
password s3cr3t
username app
To pull out a single value without the decoration, which is what scripts want, use -field:
vault kv get -mount=secret -field=password myapp/config
s3cr3t
Now change a value. There is a trap here. kv put replaces the whole secret with exactly the keys you give it, so this command silently drops username:
vault kv put -mount=secret myapp/config password=new-password
The result is version 2 with only one key. To change one key and keep the rest, use kv patch:
vault kv patch -mount=secret myapp/config password=another-password
That produces version 3, changing only password. Because the earlier put had already dropped username, it is still gone, and patch cannot bring it back. The lesson is to reach for patch whenever you mean "update this one field", and to use put only when you intend to replace everything.
Each write made a new version, and you can read an older one:
vault kv get -mount=secret -version=1 myapp/config
vault kv metadata get -mount=secret myapp/config
The metadata command shows every version, when it was created and whether it was deleted or destroyed. KV version 2 keeps the latest 10 versions by default. You can change that per secret, and you can make old versions expire:
vault kv metadata put -mount=secret -max-versions=5 -delete-version-after=720h myapp/config
Listing works on a path prefix:
vault kv list -mount=secret myapp
Keys
----
config
A trailing slash on a listed key means it is a folder rather than a secret.
Three ways to get rid of a secret
Version 2 distinguishes between removing and erasing:
vault kv delete -mount=secret myapp/config # soft delete the latest version
vault kv undelete -mount=secret -versions=3 myapp/config # bring it back
vault kv destroy -mount=secret -versions=1 myapp/config # permanent, cannot be undone
vault kv metadata delete -mount=secret myapp/config # every version and the metadata
A soft delete marks a version as deleted but keeps the data, so undelete can restore it. Destroy wipes the data of the given versions for good. kv metadata delete removes the secret entirely. There is also vault kv rollback, which writes an old version's contents as a new version:
vault kv rollback -mount=secret -version=1 myapp/config
vault kv get secret/myapp/config (without -mount) still works and adds data/ for you. The -mount=secret form is preferred in current documentation because it leaves no doubt about where the engine ends and your path begins.
If you want to use a KV engine at another path, mount it yourself. The type name kv-v2 gives a version 2 engine, and so does -version=2 kv; a plain kv without the version flag gives the older version 1 engine:
vault secrets enable -path=team-a kv-v2
vault kv put -mount=team-a payments/stripe api_key=sk_test_example
read on the path gets the plaintext value. Secrets put on the command line also land in your shell history. For real secrets, read the value from a file or from standard input (vault kv put -mount=secret myapp/config password=- reads the value from stdin) and keep the shell history disabled in shared environments.
- Write
secret/myapp/configwith two keys. - Use
kv putwith one key, then look at the data. Did the second key survive? - Use
kv get -version=1to see the original, thenkv rollbackto restore it. - Soft-delete the latest version, read it (what do you get?), then undelete it.
Policies: deciding who may do what
So far you have acted as root, which bypasses every check. The real value of Vault appears the moment you stop doing that. A policy is how you describe the access a person or application should have, and it is usually short.
Here is a policy for an application called myapp. Save it as myapp.hcl:
path "secret/data/myapp/*" {
capabilities = ["create", "read", "update", "patch", "delete"]
}
path "secret/metadata/myapp/*" {
capabilities = ["list", "read"]
}
Read it as two rules. The first says: on any path beneath secret/data/myapp/, the token may create, read, update, patch and delete. The second says: on secret/metadata/myapp/..., it may list and read. Anything else is forbidden, because policies deny by default.
Why two paths? This is the KV version 2 detail promised earlier. The engine exposes the actual values under data/ and the bookkeeping (version list, key listing) under metadata/. A policy that mentions only secret/myapp/* matches nothing and gives a permanent "permission denied". Vault also has delete/, undelete/ and destroy/ paths for the matching operations, so grant those deliberately, not by accident.
Write the policy to Vault, and list what exists:
vault policy write myapp myapp.hcl
vault policy list
vault policy read myapp
Success! Uploaded policy: myapp
The command vault policy fmt myapp.hcl tidies formatting, and vault policy delete myapp removes it.
Path matching rules
Paths in a policy follow a few rules that are worth knowing precisely:
- An exact path, such as
secret/data/myapp/config, matches that path only. - A trailing
*, as insecret/data/myapp/*, matches everything beneath the prefix. The asterisk is only allowed at the end. - A
+matches exactly one path segment, sosecret/data/+/sharedmatchessecret/data/teamA/sharedandsecret/data/teamB/shared, but notsecret/data/teamA/x/shared. - When several rules match a path, Vault uses the most specific one. A
denycapability always overrides everything else.
path "secret/data/+/shared" {
capabilities = ["read"]
}
The capabilities, and how they map to HTTP
Capabilities correspond to HTTP verbs and to the CLI commands you run. read is an HTTP GET. create and update are POST or PUT, and Vault picks between them depending on whether the path already holds data. delete is DELETE. list is the LIST verb used by vault kv list. patch is an HTTP PATCH, used by vault kv patch. sudo is needed for a few protected administrative paths, and deny refuses outright.
A subtle example: reading a dynamic credential such as database/creds/app needs only read, even though that request creates a new database user. Vault models it as a read of a path that generates a value.
Testing a policy before you trust it
Policies are code, and you should test them. Create a token that carries only your policy and use it:
vault token create -policy=myapp -ttl=1h
Key Value
--- -----
token hvs.CAESI...
token_accessor 8Gk2...
token_duration 1h
token_renewable true
token_policies ["default" "myapp"]
identity_policies []
policies ["default" "myapp"]
Notice that the token has both myapp and default. Now try it, using a variable to keep this token separate from your root token:
VAULT_TOKEN=hvs.CAESI... vault kv get -mount=secret myapp/config
VAULT_TOKEN=hvs.CAESI... vault kv get -mount=secret other/config
The first works. The second fails:
Error reading secret/data/other/config: Error making API request.
URL: GET http://127.0.0.1:8200/v1/secret/data/other/config
Code: 403. Errors:
* 1 error occurred:
* permission denied
Read that message carefully, because you will see it constantly. The URL shows the real path (secret/data/other/config), the Code: 403 is the HTTP status, and permission denied means the token authenticated fine but its policies do not allow this path. A missing or bad token gives a different text, which we will compare in the errors section.
Two more tools shorten the guessing. You can ask Vault what a token may do on a path, and you can print the policy a command would need:
vault token capabilities hvs.CAESI... secret/data/myapp/config
vault kv get -output-policy -mount=secret myapp/config
The first prints the capabilities (for example create, delete, patch, read, update). The second prints a ready-made policy stanza for the command, which is a clever way to start writing a policy: run the command you want to allow with -output-policy, and paste the result.
data/ in KV v2 paths. (2) Giving read on data/ but no list on metadata/, so kv list fails. (3) Duplicate keys in one policy file: since Vault 1.21, duplicate attributes in HCL are an error rather than a warning. (4) Editing the policy in a file and forgetting to run vault policy write again; Vault never reads your local file.
- Write
myapp.hclas above and upload it withvault policy write. - Create a token with that policy and read
myapp/configwith it. - Try
kv listonmyapp, then onother. Explain each result. - Remove the
metadatarule, re-upload the policy, and see which command breaks.
Authentication: how people and applications log in
Using the root token to do everything defeats the point. Real users and applications log in through an auth method. Each method proves an identity in its own way and ends by issuing a token that carries policies.
List what is enabled:
vault auth list
Path Type Accessor Description
---- ---- -------- -----------
token/ token auth_token_a1b2c3d4 token based credentials
The token method is always present and cannot be removed. It is what you have been using.
Userpass: a good first method for learning
The userpass method stores usernames and passwords inside Vault. It is simple, which makes it ideal for practising, though most companies prefer single sign-on through OIDC or LDAP for people.
vault auth enable userpass
vault write auth/userpass/users/alice password=change-me-please token_policies=myapp
Two points about that command. First, the word write is generic. In Vault you configure almost everything by writing to a path, and the path here is auth/userpass/users/alice. Second, token_policies lists the policies the login token will carry. Now log in as Alice:
vault login -method=userpass username=alice
Password (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token hvs.CAESI...
token_accessor 9Lm3...
token_duration 768h
token_renewable true
token_policies ["default" "myapp"]
identity_policies []
policies ["default" "myapp"]
token_meta_username alice
The CLI saved this token in ~/.vault-token. One surprise here: if VAULT_TOKEN is still exported as root, the environment variable takes priority over the saved token, and you are still root. Run unset VAULT_TOKEN to use the token you just earned. Then check who you are:
vault token lookup
The output lists the token's policies, TTL, creation time, and whether it is renewable. Alice can now read secret/data/myapp/config and nothing else. That is the security model in action.
The same login through the HTTP API, with no CLI involved, is a POST to the login path. The response contains the token at .auth.client_token:
curl --request POST --data '{"password": "change-me-please"}' \
http://127.0.0.1:8200/v1/auth/userpass/login/alice
Logging in with a token you already have
Since tokens are the universal credential, vault login with no method takes a token and stores it:
vault login
You can also log in by passing the token value, but that exposes it in your shell history, so prefer the prompt. To discard the stored token, delete ~/.vault-token.
Other methods you will meet
You do not need these yet, but recognise the names. AppRole is for machines and automation: a role ID plus a secret ID. Kubernetes lets a pod log in with its service account. JWT/OIDC connects to single sign-on providers and to CI systems such as GitHub Actions and GitLab, so that a pipeline can log in with a short-lived identity token instead of a stored password. AWS, GCP and Azure methods let a workload log in with its cloud identity. LDAP, GitHub, Okta and TLS certificates cover other sources. The mid-level guide builds AppRole, Kubernetes and JWT step by step.
The principle behind all of them is the same. The best credential for a workload is one it already has for another reason, such as its Kubernetes service account or its cloud identity. A method that needs you to distribute a password to the client has just moved the secret-management problem one step, and later guides give it a name: the secret zero problem.
- Enable
userpassand create a user with themyapppolicy. - Run
unset VAULT_TOKEN, thenvault login -method=userpass username=alice. - Run
vault token lookup, then readmyapp/configandother/config. - Repeat the login with
curland extract the token withjq -r .auth.client_token.
Tokens, TTLs and leases in practice
Tokens are not forever, and that is deliberate. A leaked token that expires in an hour is a small problem. A leaked token that lives for a month is a large one. Understanding TTLs is the difference between a Vault that feels helpful and one that surprises you at 3 a.m.
Create a token yourself, with an explicit policy and lifetime:
vault token create -policy=myapp -ttl=1h
vault token create -policy=myapp -ttl=10m -use-limit=3
The second token expires in ten minutes and stops working after three uses. Short lifetimes and use limits are common defences for one-off jobs.
TTL, renewal and maximum TTL
A token has a TTL (how long until it expires) and a maximum TTL. While a token is renewable and under its maximum, you can extend it:
vault token renew
vault token renew -increment=30m hvs.CAESI...
Renewing resets the TTL but never beyond the maximum. When the maximum is reached, the token dies and the client must log in again. The system default TTL and maximum are both 768 hours; you can change them per mount, for example vault auth tune -default-lease-ttl=1h -max-lease-ttl=24h userpass/. A periodic token works differently: each renewal resets it to its period, so a long-running service can keep one alive indefinitely as long as it renews on time. An explicit_max_ttl is a hard cap that not even renewal can pass.
If you request a longer TTL than the maximum allows, Vault does not fail; it caps the value and prints a warning such as "TTL of ... exceeded the effective max_ttl ...; TTL value is capped accordingly". Read warnings like that, they tell you the value you asked for is not the value you got.
Looking at and revoking tokens
vault token lookup
vault token lookup hvs.CAESI...
vault token lookup -accessor 9Lm3...
vault token revoke hvs.CAESI...
vault token revoke -accessor 9Lm3...
The accessor-based versions are what you use when you know there is a token to cancel but you must not see the token itself, for example when going through an audit log. Revoking a token also revokes all child tokens and every lease it created, so revoke high in the hierarchy when dealing with an incident.
Leases: the same idea for dynamic secrets
When Vault creates a dynamic secret, it attaches a lease. Suppose you later enable the database engine and read a credential. The response contains a lease_id, a duration and a flag saying whether you can renew. The commands are symmetrical with tokens:
vault lease lookup <lease_id>
vault lease renew -increment=1h <lease_id>
vault lease revoke <lease_id>
vault lease revoke -prefix database/creds/app
The last command is the emergency button: it revokes every outstanding credential beneath a prefix, and Vault removes the matching database users. A team that can run that command in seconds has a much better incident story than one that must rotate a shared password across twenty services.
A quick look at dynamic secrets
You do not need a database to grasp the idea. The flow is: an administrator configures the engine with a connection and a role that holds a SQL template, and an application reads database/creds/<role>. Vault connects, creates a unique user with a short life, and returns the username and password. When the lease ends, Vault drops that user. No human ever sees or stores the password, and every application instance has a different one. The mid-level guide shows the full setup. For now, hold on to the contrast with KV, where you store a secret and it stays the same until somebody changes it.
permission denied (for tokens) or invalid lease ID or lease not found (for leases). If something worked yesterday and not today, check the TTL first with vault token lookup before you suspect the policy.
- Create a token with
-ttl=2m -use-limit=2and themyapppolicy. - Use it twice to read a secret. Try a third time and read the error.
- Create a short-lived token, run
vault token lookupon it twice a minute apart and watchttlfall. - Create a parent token and a child; revoke the parent and confirm the child is dead.
Talking to Vault over HTTP
Every CLI command is an HTTP request in disguise. Knowing this demystifies Vault, and it is how your applications will talk to it, using a client library or plain HTTP.
The API lives at /v1/. A token goes in the X-Vault-Token header. To read the secret from earlier:
curl --header "X-Vault-Token: $VAULT_TOKEN" $VAULT_ADDR/v1/secret/data/myapp/config
The JSON response has the structure that catches people out:
{
"request_id": "0f3a...",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"data": {
"password": "s3cr3t",
"username": "app"
},
"metadata": {
"created_time": "2026-10-01T09:30:12.123456Z",
"version": 1
}
}
}
There are two data keys. The outer one is Vault's standard response envelope, used by every engine. The inner one is KV version 2's wrapper around your key-value pairs, alongside metadata. With jq you pull the password with .data.data.password. KV version 1 has no inner wrapper.
To write, send a POST with the same inner data wrapper:
curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST \
--data '{"data": {"username": "app", "password": "s3cr3t"}}' \
$VAULT_ADDR/v1/secret/data/myapp/config
Other endpoints you will want early are the health check and seal status, neither of which needs a token:
curl $VAULT_ADDR/v1/sys/health
curl $VAULT_ADDR/v1/sys/seal-status
The health endpoint's status code carries meaning, which is what load balancers and monitors rely on: 200 means the node is active and unsealed, 429 an unsealed standby, 501 not initialised, and 503 sealed.
Remember the trick from earlier: any CLI command plus -output-curl-string prints the equivalent request rather than running it.
vault kv get -mount=secret -output-curl-string myapp/config
curl -H "X-Vault-Request: true" -H "X-Vault-Token: $(vault print token)" http://127.0.0.1:8200/v1/secret/data/myapp/config
Since Vault 2.0, request paths must be clean. A path containing //, /./ or /../ is rejected. If your script builds URLs by joining strings, such as adding a trailing slash to a mount and then a leading slash to a path, it will break on 2.x with a clear refusal. Strip the stray slashes.
-format=json flag (or VAULT_FORMAT=json) for scripts that parse CLI output with jq, and -field=name when you want one raw value. Never scrape the default table output; its layout is not a contract.
- Fetch
myapp/configwithcurland print only the password withjq. - Write a new version through
curland checkkv metadata getshows version 4 (or higher). - Call
/v1/sys/healthand read the HTTP status withcurl -i. - Use
-output-curl-stringonvault policy listand run the printed command.
Audit logs: proving who did what
A secrets server that cannot tell you who read a secret is only half a security tool. Audit devices record every request and response. You enable one much like an auth method:
vault audit enable file file_path=/tmp/vault-audit.log
vault audit list -detailed
On a dev server, /tmp is fine. In real deployments the log goes to a protected directory, or to syslog, or to a socket that feeds a central logging system. Each line is a JSON object describing one request or response. The entries contain the path, the operation, the client's identity and the result. Secret values and tokens are not written in plain text: Vault replaces them with an HMAC-SHA256 hash, so you can correlate "the same value was used twice" without the log becoming a second copy of your secrets.
Do a read and look at the log:
vault kv get -mount=secret myapp/config
tail -n 2 /tmp/vault-audit.log | jq .
You will see a request entry and a response entry. The request shows "operation": "read" and "path": "secret/data/myapp/config", plus the display_name of the token holder (for example userpass-alice), and the values in the response are hashed strings beginning with hmac-sha256:.
There is a rule to learn now, because it explains a famous class of outages. If at least one audit device is enabled and none of them can write, Vault stops answering requests. It fails closed: no audit record means no request is served. A full disk that holds the audit log can therefore take down every application that depends on Vault. This is why production guides say to run at least two audit devices, to watch the disk and to alert on audit failures.
- Enable a file audit device on your dev server.
- Do one
kv getas alice and one as root. - Find both requests in the log with
jqand compare theirdisplay_namefields. - Look up the password value in the log and confirm it is hashed.
Configuration: from dev mode to a real server
A development server needs no configuration file. A real server reads one. Even if you do not run production yourself, you will read these files in your job, so know what the pieces are.
A minimal single-node configuration using Integrated Storage (Raft) looks like this:
ui = true
disable_mlock = true
api_addr = "https://vault.example.com:8200"
cluster_addr = "https://vault.example.com:8201"
storage "raft" {
path = "/opt/vault/data"
node_id = "vault-1"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/opt/vault/tls/tls.crt"
tls_key_file = "/opt/vault/tls/tls.key"
}
Go through it piece by piece.
ui = trueserves the web interface on the same port as the API.disable_mlockcontrols whether Vault asks the operating system to keep its memory out of swap. With Integrated Storage, the setting has no default since Vault 1.20, so the server refuses to start until you set it explicitly totrueorfalse. In containers it is normallytrue, and you then disable swap on the host or encrypt it.api_addris the address that clients should use, and standby nodes use it to redirect clients to the active node.cluster_addris the address nodes use to talk to each other, on port 8201. Raft requires it.storage "raft"puts the encrypted data on local disk and lets several Vault nodes form a cluster.pathis where the data lives andnode_idnames this node. Every production cluster needs three or five nodes, and the operator guide later in the series covers it.listener "tcp"is the network endpoint. The certificate and key files give it TLS. The settingtls_disable = trueexists, but it belongs only in a dev setting.- An optional
seal "awskms" { ... }(orazurekeyvault,gcpckms,ocikms,transit) block enables auto-unseal.
Run it, point the CLI at it, and initialise:
vault server -config=/etc/vault.d/vault.hcl
export VAULT_ADDR='https://vault.example.com:8200'
vault operator init
Unseal Key 1: 8kQ...
Unseal Key 2: 3vP...
Unseal Key 3: Zx1...
Unseal Key 4: mT9...
Unseal Key 5: Lw7...
Initial Root Token: hvs.xxxxxxxxxxxxxxxxxxxxxxxx
Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.
That output appears exactly once. Vault does not keep the unseal keys or show them again. Give each share to a different trusted person, and store the initial root token only long enough to set up real administrators. Then unseal by running the next command three times, each time with a different share:
vault operator unseal
After the third share, vault status shows Sealed false. On a server with auto-unseal you skip this step, because init produces recovery keys and the server unseals itself on start.
If Vault does not start, or you want to check a config file before using it, vault operator diagnose -config=/etc/vault.d/vault.hcl reports problems such as unreadable certificates, a missing cluster_addr or a storage path it cannot write.
The installer packages on Linux create a vault system user, a systemd service and a data directory. The service runs vault server against /etc/vault.d/vault.hcl, and you manage it with sudo systemctl start vault and sudo journalctl -u vault. Two security habits from the official hardening guide are worth memorising at this stage: run Vault as an unprivileged user, and never leave the root token around.
If you run Vault on Kubernetes, the official Helm chart deploys it. The default chart values start a single node with file storage, which HashiCorp itself says is not production-ready; a real install turns on high availability with Raft (server.ha.enabled=true and server.ha.raft.enabled=true). The chart version at the time of writing deploys a slightly older Vault than 2.1.1 unless you pin server.image.tag.
Environment variables configure the client, and you will set them in every terminal:
| Variable | What it does |
|---|---|
VAULT_ADDR |
The server URL the CLI talks to |
VAULT_TOKEN |
The token to use, taking priority over ~/.vault-token |
VAULT_FORMAT |
Output format: table, json or yaml |
VAULT_CACERT |
Path to a CA certificate to trust for TLS |
VAULT_SKIP_VERIFY |
Turns off TLS verification; for throwaway dev work only |
VAULT_NAMESPACE |
Namespace to use on Enterprise and HCP editions |
VAULT_SKIP_VERIFY=true. It removes the protection that TLS gives, so anyone on the network path can impersonate your Vault. If you see a certificate error, the right fix is to give the client the CA with VAULT_CACERT.
- Write the
vault.hclabove with a localpathyou own andtls_disable = truein the listener (local learning only; remove the certificate lines). - Run
vault operator diagnose -config=vault.hcland read its checks. - Start the server with that file, run
vault operator init -key-shares=3 -key-threshold=2, and unseal with two shares. - Stop the server, start it again, and confirm it comes back sealed.
Reading the errors Vault gives you
Most beginner frustration with Vault comes from five or six messages. Each one has a cause that you can identify quickly once you know what the message is telling you.
| Message | What it means | What to do |
|---|---|---|
http: server gave HTTP response to HTTPS client |
The CLI is speaking HTTPS to a plain HTTP server | export VAULT_ADDR='http://127.0.0.1:8200' |
connection refused on port 8200 |
Vault is not running, or listens elsewhere | Start it; check the listener address |
Code: 503 ... Vault is sealed |
The server needs unsealing | vault operator unseal, or fix access to the KMS |
Code: 403 ... permission denied |
The token is valid but the policy does not allow the path | vault token capabilities, then fix the policy |
Code: 403 ... missing client token |
No token was sent | vault login, or set VAULT_TOKEN |
no handler for route |
Wrong path, or nothing is mounted there | vault secrets list; check spelling |
x509: certificate signed by unknown authority |
The client does not trust the server's CA | Set VAULT_CACERT |
Vault is not initialized (health returns 501) |
Fresh storage | vault operator init |
The distinctions matter. permission denied and missing client token both use HTTP 403, yet they point in opposite directions. The first says you are known but not allowed; the second says you never identified yourself. no handler for route is the same story at another level: the path did not reach any engine at all, so the problem is not policy but a typo or an engine that was never enabled.
Follow a fixed order when a read fails, and you will rarely be stuck for long:
- Is the server reachable, and does
vault statussaySealed false? - Is the token what you think it is? Run
vault token lookupand check policies and TTL. Check thatVAULT_TOKENis not overriding the token you logged in with. - Is the path right? Run
vault secrets listand check that the mount exists and that the KV version matches your path style. - What does the policy allow? Use
vault token capabilities <path>, and usevault read sys/internal/ui/resultant-aclto see the effective policy of your current token. - Still unclear? Use
-output-curl-stringto see the real HTTP request, and read the audit log.
One error worth singling out is this one from the KV CLI: preflight capability check returned 403, please ensure client's policies grant access to path "secret/". The CLI asks Vault which KV version a mount uses, and it needs read on sys/internal/ui/mounts/secret to do so. The default policy normally covers this. If a custom setup leaves it out, either add that read or call the explicit API path.
- Cause each of these on purpose: a wrong
VAULT_ADDRscheme, a misspelled mount, an expired or emptyVAULT_TOKEN, and a path outside your policy. - For each, write the message, the layer that failed (network, mount, identity, policy) and the one command that diagnosed it.
Putting it all together
Here is a small end-to-end project that uses every idea in this guide. Suppose a small team builds a service called orders and needs a database password and an API key. You are the Vault administrator, and two people will use the secrets: a developer, Dina, who may read and update them, and a read-only auditor, Omar.
Start a dev server in one terminal with vault server -dev -dev-root-token-id=root. In another, set up the client:
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='root'
vault audit enable file file_path=/tmp/vault-audit.log
vault secrets enable -path=orders kv-v2
Store the secrets under a path that reflects the service:
vault kv put -mount=orders prod/database username=orders_app password=Pa55-from-admin
vault kv put -mount=orders prod/payments api_key=sk_test_example
Write two policies, one for each role:
path "orders/data/prod/*" {
capabilities = ["create", "read", "update", "patch"]
}
path "orders/metadata/prod/*" {
capabilities = ["list", "read"]
}
path "orders/data/prod/*" {
capabilities = ["read"]
}
path "orders/metadata/prod/*" {
capabilities = ["list", "read"]
}
Upload the policies and create both users:
vault policy write orders-dev orders-dev.hcl
vault policy write orders-audit orders-audit.hcl
vault auth enable userpass
vault write auth/userpass/users/dina password=dina-demo-pass token_policies=orders-dev token_ttl=1h
vault write auth/userpass/users/omar password=omar-demo-pass token_policies=orders-audit token_ttl=1h
Now act as each person. Log in as Dina, rotate the API key with patch, and list the secrets:
unset VAULT_TOKEN
vault login -method=userpass username=dina
vault kv patch -mount=orders prod/payments api_key=sk_test_rotated
vault kv list -mount=orders prod
Then log in as Omar. He can read, but any write is refused:
vault login -method=userpass username=omar
vault kv get -mount=orders -field=api_key prod/payments
vault kv put -mount=orders prod/payments api_key=nope
The last command fails with permission denied, and that is the right result. Finally, look at the audit log to see the trail and to confirm it recorded Omar's refused write with his identity:
grep '"operation":"update"' /tmp/vault-audit.log | jq -r '.auth.display_name + " " + .request.path + " " + (.error // "ok")' | tail -n 3
Finish with cleanup that shows the lifecycle: look at Dina's token, revoke it, and watch her next request fail.
vault login -method=userpass username=dina
vault token revoke -self
vault kv list -mount=orders prod
You have just run the full loop. Secrets were stored in a central place, two identities received least-privilege access, every action was logged, and a token was revoked on demand. Replace the two human users with AppRole or Kubernetes login for applications, and the shape of a real deployment appears.
- Run the whole project from an empty dev server, without copying: write each command yourself.
- Add a third user who may read only
prod/payments, notprod/database. - Change Omar's policy to deny
orders/data/prod/databaseexplicitly and confirm the deny wins over a broader read rule.
What you can now do, and what comes next
You can now explain what Vault is and the problem it solves, and trace a request from login through token and policy to a secrets engine. You can install it, run a development server, and tell dev mode apart from a real deployment. You can store, version, patch, delete and restore secrets in KV version 2, and you know why its paths contain data/. You can write a policy, test it with a real token, and read permission denied as a statement about policy rather than about passwords. You can log in through userpass, control token lifetimes, renew and revoke tokens, call the HTTP API directly, turn on an audit log, and read a basic server configuration.
Some facts from this guide are worth keeping in your head as a short list. The CLI defaults to HTTPS, so VAULT_ADDR matters. kv put replaces and kv patch merges. KV version 2 policies need data/ and metadata/. Root tokens are for setup, not for daily use. A sealed server is a healthy server waiting for keys. And if audit devices are enabled but cannot write, Vault stops serving requests.
What comes next depends on where you work:
- Mid-level covers machine authentication (AppRole, Kubernetes, JWT for CI), dynamic database credentials, Transit encryption, PKI certificates, response wrapping, and Vault Agent and the Vault Secrets Operator for delivering secrets to applications.
- Senior covers running Vault as a platform: Raft clusters, auto-unseal, upgrades, backup and restore, monitoring, multi-tenancy and incident playbooks.
- Neighbouring guides in this catalogue fit naturally. Vault is often deployed with Kubernetes and configured as code with Terraform, and the applications that use it often run in Docker containers.
For teams in the Gulf and Egypt, one practical point recurs in security reviews: where secrets and their encryption keys physically live. A self-managed Vault lets you keep the cluster, the audit logs and the auto-unseal key in a cloud region inside your data-residency boundary, which is often the reason a regulated employer prefers it to a managed secrets product in another jurisdiction. Raise that question early when you design a deployment.
Two last reminders about the ecosystem. Older tutorials may show Vault 1.x details (tokens starting s., unauthenticated rekey endpoints, or the HCP Vault Secrets service, which has reached end of life). And the quickest way to stay correct is to read the release notes at the start of any new project.
Sources
- Vault documentation
- Install Vault
- Vault release notes
- Important changes in Vault
- Seal and unseal concepts
- Tokens
- Policies
- KV version 2 secrets engine
- Audit devices
- Server configuration
- Integrated Storage (Raft) configuration
- Production hardening
- Health API
- CLI command reference
- Deploying Vault on Kubernetes with Helm