Home / Alt manpages / pam_motd(8)

  • pam_motd(8)
  • Admin command
  • linux

Configure and Test a Reliable Linux MOTD with pam_motd

Every login banner that greets you on an Ubuntu box is assembled by pam_motd, and almost nobody looks at how until it shows stale or duplicated text. This guide gets you a predictable login message, a reversible local MOTD fragment, and checks that prove the PAM configuration and generator are doing what you expect. The examples match libpam-modules version 1.5.3-5ubuntu5.7 on the machine used for this guide.

Allow about fifteen minutes. You need shell access and, for the configuration steps, sudo access. Reading the current files is safe. Editing PAM configuration or a root-owned MOTD script changes login behaviour, so keep an existing session open while testing.

1. Check which path your PAM stack uses

Start by finding the pam_motd entries rather than assuming a distribution uses the same arrangement as another machine:

$ grep -n pam_motd /etc/pam.d/login /etc/pam.d/sshd
  • Two-stage entries. On this Ubuntu installation the entries use /run/motd.dynamic first and then call pam_motd.so noupdate.
  • First call updates. The first invocation permits the update-motd scripts to refresh the dynamic file.
  • Second call displays. The second shows the file without running those scripts again.
  • Plain entry means defaults. A bare session optional pam_motd.so line means the module uses its normal search paths instead.

Checkpoint

The entries must be in the session part of the PAM stack. The module only provides a session type. Do not add it to an authentication line, and do not remove an existing login method while experimenting.

2. Understand the default search and precedence

  • Regular files, first match wins. With no module options, pam_motd tries /etc/motd, /run/motd, and /usr/lib/motd in that order, and the first one that exists is used.
  • Then the directories. It scans /etc/motd.d, /run/motd.d, and /usr/lib/motd.d. A file in a higher-priority directory replaces a same-named file lower down, and the rest display in lexicographic order.
  • Size cap. Each message is limited to 64 KiB.
  • Read as the logging-in user. Files in the directories are read with the credentials of the user logging in, so a private file can be silently absent for one account but visible to another.
  • Marker variable. The module sets MOTD_SHOWN=pam after processing, including when a message has been silenced.

Inspect the current state without changing it:

$ ls -l /etc/motd /run/motd /usr/lib/motd 2>/dev/null
$ find /etc/motd.d /run/motd.d /usr/lib/motd.d -maxdepth 1 -type f -printf '%p\n' 2>/dev/null | sort

3. Add one dynamic fragment

The Ubuntu-style update-motd framework runs executable scripts in /etc/update-motd.d as root at login, concatenating their standard output into /run/motd.dynamic. Use a two-digit prefix to control order, and skip a filename extension because scripts are selected with the run-parts --lsbsysinit rules.

Warning

This example changes the message shown to every local and remote login user. Choose text that is appropriate to disclose to all users, and do not put passwords, tokens, private addresses or other secrets in it.

$ sudo tee /etc/update-motd.d/90-local-notice >/dev/null <<'EOF'
#!/bin/sh
printf '\nLocal notice: maintenance window at 22:00 UTC.\n'
EOF
$ sudo chmod 0755 /etc/update-motd.d/90-local-notice

The script emits a leading blank line and ends with a newline. Check that run-parts accepts its name before a new login depends on it:

$ run-parts --test --lsbsysinit /etc/update-motd.d | grep 90-local-notice
/etc/update-motd.d/90-local-notice

To undo this example, remove only the file you created:

$ sudo rm /etc/update-motd.d/90-local-notice

Recovery

That removal is destructive for the file, so check the path carefully before pressing Return. It does not remove the generated MOTD immediately; the next permitted refresh or login rebuilds the dynamic output.

4. Refresh and inspect the generated output

On a system using the framework, run the selected fragments in order and write a temporary preview as root. This avoids replacing the live generated file while you inspect the result:

$ sudo sh -c 'run-parts --lsbsysinit /etc/update-motd.d > /tmp/motd.preview'
$ sed -n '1,80p' /tmp/motd.preview
$ test -s /tmp/motd.preview && echo 'preview is non-empty'

The preview should contain the output from your fragment at the position implied by its name. Watch for these silent failure modes:

  • Not executable. A script without the execute bit is skipped.
  • Disallowed name. Names that fail the run-parts --lsbsysinit rules are skipped.
  • Wrote to standard error only. Only standard output is captured.
  • Exited before printing. A crash before the printf line leaves nothing behind.
  • Slow network calls. Use cached output rather than delaying every login.

On Ubuntu, /etc/motd is commonly a symbolic link to /run/motd.dynamic. Check rather than assume:

$ ls -l /etc/motd /run/motd.dynamic
$ test -r /run/motd.dynamic && sed -n '1,80p' /run/motd.dynamic

Do not overwrite /run/motd.dynamic by hand as a permanent fix. It is runtime output and the next update can replace it. Change the responsible fragment instead.

5. Use a static or private source when dynamic output is not wanted

A PAM option can select a different regular file:

session optional pam_motd.so motd=/srv/login-message

You can also pair it with a directory:

session optional pam_motd.so motd=/srv/login-message motd_dir=/srv/login-message.d

Tip

Specifying either motd= or motd_dir= is a complete replacement for the defaults, disabling the default behaviour for both option groups. If you want the normal paths as well, name them explicitly as colon-separated values, for example motd=/srv/login-message:/etc/motd:/run/motd:/usr/lib/motd. Review the full PAM line before saving it, and test in a second session.

Use noupdate when a stack should display existing files without running /etc/update-motd.d. It suits the second display pass, but it is not a general switch for disabling the MOTD.

Done means

  • Entry located. The relevant PAM file has a session pam_motd.so entry.
  • Mode understood. You know whether the host uses dynamic output, static files, or both.
  • Fragment is safe. Custom fragments are executable, safely named, ordered deliberately, and free of secrets.
  • Fragment confirmed. run-parts --test --lsbsysinit lists the fragment and a preview contains its output.
  • Verified live. A second login session confirms the result before the original session is closed.
  • Rollback ready. The rollback path is clear: remove the local fragment or restore the previous PAM line.