Configure and Test a Reliable Linux MOTD with pam_motd
You will finish with a predictable login message, a reversible local MOTD fragment, and checks that show whether 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.
The route
Jump straight to the step you need, or tick off Done means at the end.
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 that a distribution uses the same arrangement as another machine:
$ grep -n pam_motd /etc/pam.d/login /etc/pam.d/sshd
On this Ubuntu installation the entries use /run/motd.dynamic first and then call pam_motd.so noupdate. The first invocation permits the update-motd scripts to refresh the dynamic file. The second displays it without running those scripts again. If your files contain a plain session optional pam_motd.so line, the module uses its normal search paths instead.
Checkpoint: the entries must be in the session part of the PAM stack. The module provides only 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
With no module options, pam_motd tries the regular files /etc/motd, /run/motd, and /usr/lib/motd. For that regular-file list, the first existing file wins. It then scans /etc/motd.d, /run/motd.d, and /usr/lib/motd.d. A file in a higher-priority directory replaces a same-named file in a lower-priority directory, and the remaining names are displayed in lexicographic order.
Each message is limited to 64 KiB. Files in the directories are read with the credentials of the user logging in. That affects private files and can make a message silently absent for one account but visible to another. 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 omit a filename extension because the scripts are selected using 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 allowing a new login to depend 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
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 will rebuild 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. A script that is not executable, has a disallowed name, writes to standard error only, or exits before printing will not provide the intended fragment. Long network calls should 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 can be replaced by the next update. 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
These are complete replacements for the defaults: specifying either motd= or motd_dir= disables the default behaviour for both option groups. That is an easy trap. 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 is useful for the second display pass, but it is not a general switch for disabling the MOTD.
Done means
- The relevant PAM file has a
sessionpam_motd.soentry. - You know whether the host uses dynamic output, static files, or both.
- Custom fragments are executable, safely named, ordered deliberately, and free of secrets.
run-parts --test --lsbsysinitlists the fragment and a preview contains its output.- A second login session confirms the result before the original session is closed.
- The rollback path is clear: remove the local fragment or restore the previous PAM line.