How to install curses.h (ncurses) library on Linux

How to install curses.h (ncurses) library on Linux

Linux curses programming starts with one header file: curses.h. Any program that needs to move a cursor, paint colors or build a text-based menu without shelling out to raw terminal escape codes needs it.

This guide helps you install curses.h through the ncurses library on Ubuntu, Fedora, Arch and Alpine, along with the basics of writing, compiling and running a Linux curses program once the header is in place.

What is curses and how does it relate to ncurses?

Curses is a terminal control library for Unix-like systems that lets programs build text user interfaces (TUIs) without writing separate code for every terminal type. Ken Arnold wrote the original curses library in 1978 for BSD Unix, where it powered early games like Rogue.

A separate AT&T System V version added color, reverse-video support and line-drawing that the BSD original lacked. curses.h is simply the header file that ships with the ncurses (new curses) implementation, so installing curses.h means installing ncurses.

Pavel Curtis wrote ncurses as pcurses in 1982 and it was reissued as ncurses 1.8.1 in late 1993. The name curses itself is a pun on “cursor optimization,” the technique the library uses to redraw only the parts of the screen that changed.

Most curses implementations, ncurses included, look up a terminal’s capabilities in a database called terminfo rather than hardcoding escape codes for every terminal type. An older, simpler format called termcap served the same purpose in early BSD curses.

Because ncurses reads from this database at runtime, one compiled binary draws correctly on an old VT100, a modern xterm or an SSH session.

Installing curses.h library on Linux

On Debian-based distributions like Ubuntu, installing curses.h is a two-command job: refresh the package index, then pull in the ncurses development files. The steps below use apt, the default package manager on Ubuntu and Debian.

Step 1: Update your system

Refresh the package lists before installing anything new, so apt knows about the latest available versions:

sudo apt update && sudo apt upgrade

You need sudo access (or to be logged in as root) to run this. Skipping it occasionally still works, but an outdated package index can point apt at a version of ncurses-dev that no longer exists on the mirror.

Step 2: Install the ncurses development package

Install curses.h with:

sudo apt install libncurses-dev

libncurses-dev is the current unversioned package name on Ubuntu 22.04 and 24.04 and it pulls in curses.h along with the static and shared libraries needed to compile against it. Older guides reference libncurses5-dev and libncursesw5-dev instead.

Those names come from Ubuntu 20.04 and earlier, when the package was tied to a fixed ABI version (the “5”). They still exist as compatibility packages on current Ubuntu releases, but libncurses-dev is the one that tracks the current ncurses release and is the one apt installs by default when other packages depend on ncurses headers.

Step 3: Verify the installation

Confirm curses.h landed where the compiler expects it:

dpkg -L libncurses-dev | grep curses.h

dpkg -L lists every file a package installed. Piping it through grep narrows the output to curses.h itself, which should show up under /usr/include. If nothing prints, the install step above did not complete successfully and needs a retry.

Step 4: Remove the ncurses library package

To uninstall curses.h and its supporting libraries, run:

sudo apt remove libncurses-dev

Be careful with this one. Removing libncurses-dev can pull down any other installed package that links against it, including compiled TUI tools already on the system, since apt treats shared dependencies as fair game for removal.

Installing curses on other Linux distributions

The package name and manager change across distributions, but the underlying library is the same ncurses.

On Fedora, RHEL, AlmaLinux, CentOS Stream or Rocky Linux, install it with dnf, the package manager that replaced yum as the default on these distributions:

sudo dnf install ncurses-devel ncurses

yum still works as an alias for dnf on most current RHEL-family systems, so sudo yum install ncurses-devel runs the same command underneath.

On Arch Linux and Arch-based distributions, pacman handles it in one line:

sudo pacman -S ncurses

On Alpine Linux, the package manager is apk and the subcommand is add, not install:

apk add ncurses-dev

Running one of these distributions inside WSL instead of natively changes nothing about the install command itself. A Fedora WSL2 instance still uses dnf and the ncurses-devel package installs and links exactly as it would on bare-metal Fedora.

Fixing “curses.h: no such file or directory”

This compiler error means the ncurses development headers are not installed, or the compiler cannot find them. It shows up when a Makefile or manual gcc invocation includes #include <curses.h> but the matching dev package was never installed, only the runtime library.

The fix is to repeat the install step for your distribution above. If the error persists after installing the dev package, check that gcc is picking up the standard include paths with gcc -print-search-dirs, since a custom toolchain or container image can sometimes omit /usr/include from the search path.

A related but different error, “cannot find -lncurses” or “undefined reference to initscr,” happens at the link stage rather than compile time. That means curses.h was found but the -lncurses flag was left off the gcc command, or only the runtime library was installed rather than the dev package.

Writing a minimal curses program in C

Every curses program follows the same four-step shape: start the library, draw something, wait for input, then shut it down cleanly. Skipping the shutdown step is the most common way to leave a terminal in a broken state after a crash.

#include <curses.h>

int main(void) {
    initscr();              /* start curses mode */
    printw("Hello, curses!");
    refresh();               /* push the buffer to the screen */
    getch();                  /* wait for a keypress */
    endwin();                 /* restore the terminal */
    return 0;
}

Compile it with gcc program.c -o program -lncurses, linking the ncurses library explicitly since curses.h alone only declares the functions. initscr sets up the internal data structures and returns a window object called stdscr that represents the full terminal.

printw writes to that buffer and refresh sends the accumulated changes to the actual screen, since curses batches updates rather than redrawing after every call.

Any plain-text editor works for writing the source file itself, whether that’s Nano or Vim on the same machine or an editor over SSH. The curses program only cares about the compiled binary, not what produced the .c file.

Windows, stdscr and screen refresh in curses

Curses treats the whole display as a window structure named stdscr, created automatically the moment the library initializes. curses also tracks a second internal window, curscr, which mirrors what is currently visible on the physical screen. Programs are not meant to touch curscr directly.

You can create additional windows with newwin, or subwindows that share a parent’s buffer with subwin, useful for splitting the screen into panes that redraw independently.

Refreshing many windows at once is more efficient through wnoutrefresh, which updates curses’s internal model of the desired screen, followed by a single doupdate call that pushes every accumulated change to the terminal in one pass, avoiding the flicker of refreshing each window separately.

A pad is a special kind of window that can be larger than the visible screen. Only a portion of it displays at a time, which is handy for scrollable content like a long log file or a help screen that does not fit in the terminal.

Adding colors and attributes

Enable color support right after initscr with start_color(), then check has_colors() to confirm the terminal actually supports it before using any color calls. curses initializes 8 base colors like COLOR_RED and COLOR_BLUE the moment start_color runs.

Colors in curses come as pairs, not independent foreground and background values. init_pair(1, COLOR_WHITE, COLOR_BLUE) defines pair 1 as white text on a blue background and color_pair(1) returns the attribute value to pass into a draw call like addstr.

This pairing model dates back to hardware terminals that could only show a handful of colors at once and it still trips up newcomers expecting to set foreground and background separately.

Attributes like A_BOLD, A_UNDERLINE and A_REVERSE combine with color pairs using a bitwise OR, though not every terminal renders every combination distinctly. A common pattern in larger programs is caching color pair numbers in a lookup table keyed by the foreground and background combination, rather than hardcoding pair numbers throughout the code.

Reading keyboard input in curses

getch() is the basic input call. It blocks until a key is pressed and returns an integer: ASCII codes for regular keys or values above 255 for special keys like arrows, matched against constants such as KEY_LEFT or KEY_HOME. Call keypad(stdscr, TRUE) first, or those special keys arrive as multi-byte escape sequences instead of single constants.

raw() or cbreak() mode is what makes getch respond to a single keypress immediately instead of waiting for Enter, which is how canonical terminal input normally works. nodelay(stdscr, TRUE) makes getch non-blocking, returning ERR immediately if no key is waiting.

One quirk worth knowing: curses imposes a delay on the Escape key by default, because terminals send arrow keys and function keys as escape sequences that start with the same byte. That delay makes a standalone Esc press feel sluggish.

It is safe to shorten it to around 25 milliseconds by setting the ESCDELAY environment variable before calling initscr, without breaking recognition of real escape sequences.

Curses companion libraries and alternatives

ncurses ships with more than the core screen-painting functions. The panel library stacks and manages overlapping windows, menu builds selectable text menus and form handles labeled input fields, all built on top of the same window model covered above.

For line editing, such as a command prompt with history and cursor movement, most programs reach for GNU readline instead of building it on raw curses, since curses only provides a bare getstr call for reading a whole line.

Higher-level toolkits like Urwid, a Python library built on top of curses concepts, add widgets such as scrollable lists and dialog boxes that curses itself does not provide.

Key takeaways

  • curses.h ships with the ncurses library, so installing curses.h means installing ncurses
  • On current Ubuntu, install with sudo apt install libncurses-dev
  • libncurses5-dev and libncursesw5-dev are legacy Ubuntu 20.04-era package names
  • Fedora, RHEL, Alma, CentOS and Rocky use sudo dnf install ncurses-devel ncurses
  • Arch installs it with sudo pacman -S ncurses, Alpine with apk add ncurses-dev
  • Link with -lncurses when compiling, since the header alone is not enough
  • Every curses program should end with endwin() to restore the terminal
  • Colors in curses come as foreground and background pairs, not separate values

Frequently asked questions

What package do I install for curses.h on Ubuntu?

Install libncurses-dev with sudo apt install libncurses-dev. It is the current unversioned package name and replaces the older libncurses5-dev and libncursesw5-dev packages from Ubuntu 20.04 and earlier.

What is the difference between curses and ncurses?

Curses is the original System V library from AT&T. ncurses is a free, open-source reimplementation with the same API, released as ncurses 1.8.1 in 1993 and now the Linux standard.

Why do I still get “curses.h: no such file or directory” after installing ncurses?

The runtime ncurses package alone does not include curses.h. Install the development package, such as libncurses-dev on Ubuntu or ncurses-devel on Fedora, which ships the headers separately.

How do I compile a C program that uses curses.h?

Compile with gcc program.c -o program -lncurses to link the ncurses library. Leaving off -lncurses produces “undefined reference” errors for every curses function used.

Does Alpine Linux use apt to install ncurses?

No. Alpine uses its own package manager called apk. The install command is apk add ncurses-dev rather than apt or apk install.

Can I use curses.h on Windows?

Not directly outside WSL. Native Windows apps use PDCurses, which implements the same calls as ncurses but targets Win32 consoles. Inside Windows Terminal running WSL, regular Linux ncurses works fine.

Conclusion

curses.h has been the standard route to text-mode interfaces on Unix-like systems for more than four decades and ncurses keeps that API alive on every current Linux distribution. Installing it is a one-line job once the right package name for the distribution is known.