| Tested on | RHEL 10.2 with Go 1.27.0 |
|---|---|
| Package | go 1.27.0golang.org/x/term v0.45.0 |
| Applies to | Go 1.25+ |
| Privilege | Normal user |
| Scope | Hide password input with term.ReadPassword, term.IsTerminal, non-TTY handling, and the difference between no-echo and asterisk masking. Does not cover authentication design or credential storage. |
| Related guides | Golang os package Environment variables in Go Getting started with Go Golang context |
For a CLI that needs to accept a password without showing typed characters, term.ReadPassword from golang.org/x/term is the simplest approach. It reads one line from a terminal while disabling local echo, so the password is not displayed as the user types.
ReadPassword hides input completely. It does not automatically display ***** while typing.
Hiding input only affects terminal display; password hashing is a separate storage and verification concern.
Install golang.org/x/term
Pin the release tested in this article:
go get golang.org/x/term@v0.45.0go: downloading golang.org/x/term v0.45.0
go: downloading golang.org/x/sys v0.47.0
go: added golang.org/x/sys v0.47.0
go: added golang.org/x/term v0.45.0The term package depends on golang.org/x/sys for platform-specific terminal control. golang.org/x/term v0.45.0 requires Go 1.25 or newer.
Hide password input with term.ReadPassword
The signature is func ReadPassword(fd int) ([]byte, error). Pass int(os.Stdin.Fd()) so you read from the same stdin handle your process uses.
Print the prompt before calling ReadPassword. ReadPassword does not echo the Enter key, so print a newline after the call to move subsequent output to the next line. The returned slice does not include the newline character.
Save this program as main.go:
package main
import (
"fmt"
"os"
"golang.org/x/term"
)
func main() {
fd := int(os.Stdin.Fd())
fmt.Print("Password: ")
password, err := term.ReadPassword(fd)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println()
if len(password) == 0 {
fmt.Fprintln(os.Stderr, "empty password")
os.Exit(1)
}
fmt.Println("Password received")
}Run it in a real terminal with go run main.go (not an IDE run pane without a TTY). Sample interaction:
Password:
Password receivedNothing appears as you type at the Password: prompt; after Enter you get a blank line and Password received. Check err before using the returned []byte.
Detect non-terminal input with term.IsTerminal
ReadPassword already attempts terminal-specific handling and returns an error when the descriptor cannot be used appropriately. term.IsTerminal(fd) independently reports whether a file descriptor is a terminal.
If your program needs different behavior for interactive and non-interactive input, check stdin with term.IsTerminal before calling ReadPassword:
fd := int(os.Stdin.Fd())
fmt.Println(term.IsTerminal(fd))When this prints false, stdin is often a pipe, a redirected file, output from another process, or a CI runner without a TTY. ReadPassword is for terminal input; pipes and redirected stdin need a separate input path.
One common pattern is to fail early with a clear message:
if !term.IsTerminal(fd) {
fmt.Fprintln(os.Stderr, "password input requires an interactive terminal")
os.Exit(1)
}Add that guard to main.go before the ReadPassword call. Piped stdin then stops before ReadPassword runs:
echo test | go run main.gopassword input requires an interactive terminal
exit status 1Does ReadPassword show asterisks?
term.ReadPassword disables echo completely; it does not print *. Asterisk masking requires reading individual keystrokes in raw mode or using a terminal/TUI library. For normal CLI password prompts, no-echo input with ReadPassword is simpler.
Common errors
| Problem | Cause |
|---|---|
inappropriate ioctl for device on Linux/Unix |
stdin is not an interactive terminal |
| Password appears on screen | Input was read with normal stdin APIs instead of terminal echo control |
No ***** appears |
ReadPassword disables echo; it does not mask with asterisks |
| Works on terminal but not pipe/CI | stdin is not a terminal; use a separate non-interactive input path |
| Extra blank or missing line after prompt | Handle prompt and newline placement explicitly |
Summary
Hide password input in Go with term.ReadPassword(int(os.Stdin.Fd())): print a prompt, read without echo, then print a newline before any further output. The function does not draw asterisks while typing.
When stdin may not be a terminal, use term.IsTerminal to branch to a separate non-interactive input path. Do not print or log the returned bytes in examples or debug output.
References
- golang.org/x/term —
ReadPasswordandIsTerminal - os package —
StdinandFd()
Frequently Asked Questions
How do I hide password input in Go?
Use golang.org/x/term ReadPassword on int(os.Stdin.Fd()). It reads one line while local echo is disabled, so characters do not appear as the user types. Print your own prompt and a newline after Enter.
Does term.ReadPassword display asterisks?
No. ReadPassword disables echo completely. It does not print one asterisk per keystroke. Masked echo requires raw terminal input handling or a TUI library.
Why does ReadPassword return inappropriate ioctl for device?
On Unix-like systems, non-terminal input may produce an error such as inappropriate ioctl for device. The exact error can vary by platform. Piped or redirected stdin is the usual cause.
Does golang.org/x/term work on Windows?
Yes. The module implements terminal helpers for Windows and Unix. Still pass int(os.Stdin.Fd()) and handle non-interactive stdin the same way you would on Linux.
Can I read a password from a pipe or CI job?
ReadPassword is for terminal input. Pipes and redirected stdin need a separate input path.
Is hiding a password the same as hashing it?
No. Hiding input controls what appears on screen while the user types. Hashing controls how a password is stored or verified later. They solve different problems.

