SSD Nodes Learn 🎉 VPS from $5.50/mo
Guides Matt ConnorBy Matt Connor

Fix 'unknown terminal type' over SSH

Your terminal sets a TERM name the server has never heard of, so curses programs fail. Fix it with a terminfo entry in your home directory, no root.

What "unknown terminal type" means

An unknown terminal type error over SSH means the name in TERM has no matching entry in the terminfo database on the server. Your emulator sets that name on your laptop. The database that has to recognise it lives on the remote machine. When the lookup fails, every curses program on that server stops before it draws anything, because it has no description of what your terminal can do.

The fix that keeps all of your emulator's features is one command: compile your own terminfo entry into ~/.terminfo on the server. ncurses searches your home directory before any system directory, so this needs no root and changes nothing for other users.

TERM is a name, terminfo is the database

TERM holds one string, and that string is only a key. terminfo is a compiled database of terminal descriptions, and the key is how a program finds the right one. A description records how many colours the terminal has, which bytes move the cursor to row 12 column 40, which bytes the F5 key sends, and whether it can draw a styled underline. ncurses, the library nearly every text user interface on Linux is built on, reads that description at startup. Programs like top, less, nano and watch cannot paint a single screen without it.

Look at your own entry:

echo "$TERM"
tput longname
infocmp -1 | head -20

tput longname prints the human-readable description of whatever TERM names. For xterm-256color it prints:

xterm with 256 colors

infocmp -1 decompiles the whole entry and prints one capability per line. If both commands work, the database on this machine knows this terminal. That is the state you need on the far side of an SSH connection too.

Why it only breaks over SSH

SSH sends the name and nothing else. When the client asks for a pseudo terminal, TERM travels with that request. The OpenSSH manual is explicit: "the TERM environment variable is always sent whenever a pseudo-terminal is requested as it is required by the protocol". So your login shell on the VPS gets TERM=xterm-ghostty, or xterm-kitty, or whatever name your emulator uses. The description behind that name stays on your laptop, in your laptop's database. A server built before your emulator existed has never heard the name. Nothing in the SSH connection itself copies terminal descriptions, and that is deliberate: the protocol carries a name, not a capability dump.

Reproduce the failure on your own machine with a name that certainly does not exist:

TERM=xterm-nosuchthing tput colors
echo "$?"
tput: unknown terminal "xterm-nosuchthing"

The exit status is 3, which ncurses documents as an unknown terminal type or a missing database. Every curses program runs the same lookup, so one missing name produces a different message from each program with a single cause behind all of them. nano stops with Error opening terminal: xterm-nosuchthing., the standard ncurses failure when it cannot load a description at startup. vim prints E558: Terminal entry not found in terminfo. less prints WARNING: terminal is not fully functional. A pager that half works and an editor that refuses to start look like unrelated bugs. They are the same missing file.

Where ncurses looks for the entry

ncurses searches in a fixed order, documented in terminfo(5):

  1. $TERMINFO, if it is set. Only that directory is searched.
  2. $HOME/.terminfo.
  3. every directory listed in $TERMINFO_DIRS.
  4. the compiled-in locations, which on Debian and Ubuntu include /etc/terminfo, /lib/terminfo and /usr/share/terminfo.

Run infocmp -D on the server to print the list that its own ncurses build uses, rather than trusting the paths above. Step 2 is the whole trick. Your home directory is searched before any system directory, so one file under ~/.terminfo fixes the lookup for your account with no package install and no root.

Fix 1: use a TERM the server already knows

The fastest unblock is to name a different terminal for one connection:

TERM=xterm-256color ssh user@203.0.113.10

Make it permanent for that host in ~/.ssh/config on your laptop:

Host vps1
    HostName 203.0.113.10
    User deploy
    SetEnv TERM=xterm-256color

SetEnv needs OpenSSH 7.8 or newer on the client, released in August 2018. TERM is the one variable that does not need the server's permission: the manual says "Similarly to SendEnv, with the exception of the TERM variable, the server must be prepared to accept the environment variable". So this works even against a hardened sshd with no AcceptEnv line at all.

What it costs: everything your emulator describes beyond plain xterm-256color. That includes 24-bit colour advertised through the RGB capability and styled underlines through Smulx. Programs can no longer see those capabilities, so they fall back to 256 colours and a plain underline. The screen works. It is simply less capable than the terminal you chose.

Do not solve this by adding export TERM=xterm-256color to your .bashrc on the server. That file runs for every connection, including connections from a terminal whose name the server does know, so it downgrades sessions that were already fine. Keep the override on the client, where you know which emulator you are running.

Fix 2: send your terminfo entry to the server once

This is the fix that keeps your emulator intact. Decompile the entry locally, pipe it over SSH, and compile it on the far side:

infocmp -x | ssh user@203.0.113.10 -- tic -x -

infocmp -x prints the entry for your current TERM as source text, including extended (user-defined) capabilities. tic -x compiles that source back into a binary description on the server. The -x on both sides matters, because without it capabilities such as Smulx are dropped in transit and you ship a downgraded copy of your own terminal.

tic writes into the system directory when it can, and falls back to $HOME/.terminfo when the user has no write access there. An ordinary account on a VPS gets the home directory, which is what you want. You can also name the destination:

infocmp -x | ssh user@203.0.113.10 -- tic -x -o '~/.terminfo' /dev/stdin

The quotes around ~/.terminfo are not decoration. ssh joins the command words into one string and hands it to the remote shell, so an unquoted ~ is expanded by your local shell first. You then send a literal /home/yourname/.terminfo to a machine where your account may be called something else. tic fails with a permission error, or it writes into a path ncurses never searches. Quote the tilde and the remote shell expands it.

Log in and check:

ssh user@203.0.113.10
tput longname
ls -R ~/.terminfo

tput longname printing your terminal's description means the lookup now succeeds, and ls shows the compiled file under a subdirectory named for the first letter of the terminal name. Then run top, or watch -n1 uptime, which redraws through the same library, to confirm a real curses program paints the screen.

What it costs: one step per server, and the entry lives in one account's home directory on one machine. Rebuild the VPS and it is gone. Put the command into whatever you use for provisioning and managing several servers at once so a new host arrives with the entry already compiled.

Fix 3: install the extended terminfo database

If you have root and want the name to resolve for every account on the box:

sudo apt update
sudo apt install ncurses-term
tput -T xterm-ghostty longname

ncurses-term holds the terminal descriptions that the base install leaves out. It fixes the name for root and for every other user, which fix 2 does not.

It only helps when your terminal's entry exists in the ncurses release your distribution packaged, so check instead of assuming. Ubuntu 24.04 ships ncurses-bin 6.4+20240113-1ubuntu2.1 (as of August 2026). Ghostty's entry was added upstream in ncurses 6.5-20241228, which is newer, so apt install ncurses-term on Ubuntu 24.04 cannot produce an xterm-ghostty entry no matter how often you run it. tput -T <name> longname is the check: it prints the description when the entry is present, and unknown terminal with exit status 3 when it is not.

To install a single entry system-wide instead of the whole package, copy the source over and compile it as root:

infocmp -x | ssh user@203.0.113.10 -- 'cat > /tmp/term.src'
ssh -t user@203.0.113.10 -- sudo tic -x -o /usr/share/terminfo /tmp/term.src

The -t on the second command allocates a pseudo terminal, so sudo can prompt for the password. Without it, sudo may exit with sudo: no tty present and no askpass program specified.

Let the emulator do the transfer

Some emulators ship a helper that performs fix 2 for you. kitty has an ssh kitten: kitten ssh myserver connects and copies kitty's terminfo to the remote host automatically. Its documentation also gives the manual form, infocmp -a xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin, with the tilde escaped for the reason described above. Ghostty documents infocmp -x xterm-ghostty | ssh YOUR-SERVER -- tic -x -, and ships shell integration (ssh-env and ssh-terminfo) plus a +ssh action that automate both fixes.

These helpers run the same commands you just ran by hand. Knowing the manual form still matters, because the helper cannot act when the connection starts somewhere else, on a jump host or inside a script.

When the error comes back

Under sudo -i. A login shell as root has HOME=/root, and sudo keeps your TERM. ncurses now searches /root/.terminfo, which does not hold your entry, so the failure returns for root alone. Confirm with sudo -i env and read the HOME and TERM lines. Install the entry into the system directory, or place a copy in /root/.terminfo.

Inside tmux or screen. These set their own TERM for the programs running inside them, commonly screen-256color or tmux-256color. An older server knows screen-256color and may not know tmux-256color, so the outer shell is healthy while everything inside the session fails. Same cause, different name. Run tput -T tmux-256color longname on the server to see which of the two it has. A terminal workbench built around tmux hides this until the day you attach on a host you have not fixed yet.

On a fresh server. ~/.terminfo is per account and per machine, so a rebuilt VPS starts empty again.

Screen draws, keys misbehave. If the display is fine but Home, End or the function keys insert stray characters, the name resolved to an entry that does not match your terminal. Key sequences come from the same terminfo entry as the drawing capabilities, so an approximate entry gives approximate keys. That is the usual result of fix 1 on an emulator whose key sequences differ from xterm. Fix 2 is the answer, because only your emulator's real entry carries the right sequences.

FAQ

Why do I only see "unknown terminal type" over SSH?

Because the database that has to hold your terminal's entry is the one on the server. Your emulator installs its description on the machine it runs on, so local programs find it without any effort from you. SSH sends only the value of TERM, since the protocol carries that name with the pseudo-terminal request. The remote ncurses looks the name up in the remote database and finds nothing, so initscr fails and the program stops.

Can I fix this without root on the server?

Yes. ncurses searches $HOME/.terminfo before any system directory, so an entry there is enough for your account. Run infocmp -x | ssh user@host -- tic -x - once per server. tic writes into your home directory when it cannot write to the system path, which is the normal case for an unprivileged account. Log in and run tput longname to confirm the lookup now resolves.

What do I lose by setting TERM=xterm-256color for a host?

Every capability your emulator describes beyond the xterm-256color entry, such as 24-bit colour through RGB and styled underlines through Smulx. Programs cannot see those capabilities, so they use the closest fallback. Function-key sequences may also differ from your real terminal. Put the setting on the client in ~/.ssh/config rather than in a .bashrc on the server, which would downgrade sessions from terminals that already worked.

Why does the error come back after sudo -i?

sudo -i starts a login shell as root with HOME=/root, while keeping your TERM value. ncurses then searches /root/.terminfo instead of your own home directory and finds nothing, so root sees the same failure your account no longer sees. Run sudo -i env and check the HOME and TERM lines. Compile the entry into the system directory with sudo tic -x -o /usr/share/terminfo, or copy it into /root/.terminfo.

Does apt install ncurses-term always fix it?

No. It adds only the entries that existed in the ncurses release your distribution packaged. Ubuntu 24.04 ships ncurses 6.4 (ncurses-bin 6.4+20240113-1ubuntu2.1, as of August 2026), and Ghostty's entry was added upstream in ncurses 6.5-20241228, so that package cannot contain it. Check with tput -T <name> longname before relying on the package, and ship the entry yourself when the check still reports an unknown terminal.

#terminfo#ssh#ncurses#tput#terminal