Changing the hostname permanently on Linux: hostnamectl, /etc/hosts and cloud-init

Published on 19 min read

hostnamectl, /etc/hostname and /etc/hosts working together, the switch against cloud-init resetting the name, and the effects on sudo, mail servers and certificates.

A server called debian, localhost or vm-01 works perfectly well. It only gets awkward once three of them are running at the same time, once log lines can no longer be told apart, or once the first mail server joins in and the remote end starts checking the name. This guide changes the hostname so that it survives the next reboot, so that sudo does not run into a timeout, and so that the services which read the name at startup know about it too.

This is the deep dive on step 6 of the checklist for a new root server. That article gives you the two commands that are usually enough. This one explains what happens behind them, and what to do when they are not enough.

Everything here applies to Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS and Ubuntu 22.04 LTS. The commands are written for running as root. As a regular user, put sudo in front of every command.

A server has three names, not one

The most common reason a rename only half works is that Linux does not know one hostname, it knows three. They live in different places, and different things overwrite them.

NameWhere it livesRead it withWhat sets it
static/etc/hostnamehostnamectl --statichostnamectl set-hostname, cloud-init
transientkernel onlyhostnamectl --transient, uname -nhostname NAME, DHCP client, systemd-networkd
pretty/etc/machine-infohostnamectl --prettyhostnamectl set-hostname --pretty

The kernel keeps the transient name, and that is the one every program receives through gethostname(). The static name is the template it is set from at boot. The pretty name may contain spaces and accented characters, and it only ever shows up in user interfaces, never on the network.

The second distinction matters just as much: which command takes its answer from where. Assume that /etc/hostname contains srv01 and that /etc/hosts holds the line 127.0.1.1 srv01.example.com srv01, then the picture looks like this.

CommandOutputSource
hostnamesrv01kernel
uname -nsrv01kernel
hostnamectl --staticsrv01/etc/hostname
hostname -ssrv01kernel, cut off at the first dot
hostname -fsrv01.example.comname resolution
hostname -dexample.comname resolution
dnsdomainnameexample.comname resolution
domainname(none)NIS domain, not DNS

The decisive row is hostname -f. That command does not read /etc/hostname. It takes the kernel name and pushes it through name resolution. The fully qualified name therefore comes from /etc/hosts or from DNS, never from the hostname file. Almost every problem in this article goes back to that one misunderstanding.

Before the first change: the way back

A rename on its own does not cut your SSH session. The dangerous part is the step after it: editing /etc/hosts. If the line for localhost disappears there, dozens of programs sit in timeouts, Postfix refuses to start, and sudo needs seconds per call. So sort out three things first.

1. Keep a second session open

Open a second terminal window with a live connection and do not close it until everything has been verified. An existing session survives every change to the hostname and to name resolution. If the connection itself is not properly set up yet, see Connecting to your server over SSH.

2. The route that bypasses SSH

KVM root servers and dedicated servers from KernelHost have no IPMI and no iDRAC. The access path that still works when nothing inside the guest system does is the VNC console in the customer panel. It hangs off the virtualization layer (or off the port itself) and does not depend on name resolution inside the guest system. Log in there once beforehand and make sure you know the root password. A rescue path you try for the first time during an emergency is not a rescue path.

3. Two copies and a rollback

cp -a /etc/hostname /root/hostname.bak
cp -a /etc/hosts /root/hosts.bak
hostnamectl > /root/hostnamectl-before.txt

That turns the way back into two lines, which you can type into the console if it comes to that:

cp -a /root/hosts.bak /etc/hosts
hostnamectl set-hostname "$(cat /root/hostname.bak)"

Taking stock in five commands

First have a look at what applies right now, and at what has a say in it.

hostnamectl
cat /etc/hostname
cat /etc/hosts
grep '^hosts:' /etc/nsswitch.conf
command -v cloud-init >/dev/null && cloud-init status --long || echo "cloud-init not installed"

The output of hostnamectl deserves a close look. It normally starts with Static hostname:. If a Transient hostname: line shows up as well, the stored name and the running name differ, and something is actively resetting the name: almost always cloud-init or a DHCP client. Turn that off first, otherwise your change is gone again after the next reboot.

The hosts: line from /etc/nsswitch.conf defines the order of name resolution. The four entries you find there mean:

  • files: /etc/hosts is read.
  • dns: the resolver queries the nameservers from /etc/resolv.conf.
  • resolve: the query goes to systemd-resolved.
  • myhostname: a systemd module that maps the local machine name to the locally configured IP addresses, even without an entry in /etc/hosts.

The last entry explains, further down, why the same mistake goes unnoticed for years on some servers.

Choosing the name: FQDN or short name

Letters, digits and the hyphen are allowed. No underscore, no dot at the beginning or the end, no hyphen at the beginning of a label. Write it in lower case: DNS compares without regard to case, while many programs compare literally. A label (the part between two dots) may be 63 characters long, the full name in DNS 253. The kernel, however, accepts at most 64 characters for the hostname, so a very long FQDN may not fit in at all. Pick the name below a domain that belongs to you: .local is reserved for mDNS, and invented endings such as .lan can turn into real top-level domains at any time.

That leaves the question of what goes into /etc/hostname. Both variants work, and they differ only in what you see displayed everywhere afterwards.

Variant/etc/hostnamehostnamehostname -f
Short name (the Debian convention)srv01srv01srv01.example.com, resolved through /etc/hosts or DNS
FQDN (many cloud images)srv01.example.comsrv01.example.comsrv01.example.com

On Debian and Ubuntu the first variant is the one to go for: the short name in /etc/hostname, the fully qualified name as the first entry in /etc/hosts. That keeps the prompt and the log lines short, and the FQDN is still correct. The second variant is just as clean, as long as you keep it up consistently. The actual mistake is mixing the two: one FQDN in /etc/hostname and a different one in /etc/hosts.

Create the A record (with IPv6, the AAAA record) for the new name right now. It costs nothing, it makes hostname -f correct even without /etc/hosts, and it is the precondition for every certificate issued on that name.

Taming cloud-init before you set anything

On images that ship cloud-init, and on Ubuntu that is practically all of them, the hostname is set at boot from the metadata of the instance. The modules in charge are set_hostname and update_hostname, plus update_etc_hosts for the file /etc/hosts. All three run early, before your own services start. Two switches control this:

grep -rE '^(preserve_hostname|manage_etc_hosts)' /etc/cloud/cloud.cfg /etc/cloud/cloud.cfg.d/ 2>/dev/null

On Ubuntu server images, /etc/cloud/cloud.cfg carries the line preserve_hostname: false, so cloud-init is allowed to touch the name. The counter switch does not belong in that file, because a package update replaces it, but in /etc/cloud/cloud.cfg.d/. The files there are read in alphabetical order and the later one wins, hence the 99:

mkdir -p /etc/cloud/cloud.cfg.d
printf 'preserve_hostname: true\n' > /etc/cloud/cloud.cfg.d/99_hostname.cfg

The second switch concerns /etc/hosts. It is easily overlooked, even though it does more damage.

Value of manage_etc_hostsEffect on /etc/hosts
not set, or falsecloud-init leaves the file alone
localhostat every boot, cloud-init makes sure that the machine name resolves, and leaves the rest of the file as it is
truecloud-init regenerates the file at every boot from the template under /etc/cloud/templates/, and your own lines are gone afterwards

If it says true and you need your own entries, set the value to localhost, or maintain the template instead (on Debian and Ubuntu that is hosts.debian.tmpl). Editing a file by hand that gets overwritten at every boot produces exactly the kind of fault you only notice weeks later.

cloud-init remembers what it set last under /var/lib/cloud/data/. That state is wiped by cloud-init clean. Please do not do this on a server that is already set up: at the next boot every module runs again, as if the machine were brand new.

The second name changer: DHCP

If the server gets its address over DHCP, the client can take the transient hostname from the reply (option 12). The static name stays untouched, which makes troubleshooting harder: hostnamectl --static shows your name, hostname a different one. Under netplan you switch it off like this:

network:
  version: 2
  ethernets:
    eth0:
      dhcp4: true
      dhcp4-overrides:
        use-hostname: false

Activate it with netplan try instead of netplan apply: try rolls the change back on its own after 120 seconds if you do not confirm. With systemd-networkd configured directly, the setting is called UseHostname=no in the [DHCPv4] section.

Setting the hostname

hostnamectl set-hostname srv01

Without further switches, hostnamectl sets the static and the transient name together. That is exactly what you want. With --static you only change /etc/hostname, and while a transient name is active the machine would keep running under the old one until the reboot. Newer systemd versions also know the short form hostnamectl hostname srv01, while set-hostname works on all four systems.

Verification: both names have to match, and the file has to carry the new content.

hostnamectl --static
hostnamectl --transient
cat /etc/hostname

Two side notes. First, hostname srv01 without the ctl changes only the transient name, and it is gone after the reboot. Second, your running shell keeps showing the old name in the prompt, because bash reads the hostname once when the session starts. That is not a failure, that is an old session. Log in again.

Optionally, you can store a pretty name that appears in user interfaces and is allowed to contain spaces:

hostnamectl set-hostname --pretty "Webserver Frankfurt"
cat /etc/machine-info

Writing /etc/hosts correctly

hostnamectl does not touch /etc/hosts. That file stays your job, and it is the reason why a rename so often only works halfway.

A line consists of an IP address, the canonical name and any number of aliases. The first name after the address is the canonical one, and that is exactly what hostname -f returns. This is why the fully qualified name comes first and the short name after it, never the other way round.

127.0.0.1       localhost
127.0.1.1       srv01.example.com srv01

::1             localhost ip6-localhost ip6-loopback
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters

Why 127.0.1.1 and not 127.0.0.1: Debian and Ubuntu deliberately keep the machine name separate from localhost. If you append the server name as an alias to the localhost line, the canonical name of that line stays localhost, and hostname -f answers with localhost. Programs that derive their own name from it then write localhost into log files and email headers.

Append a missing entry, or edit an existing line:

grep -n '^127\.0\.1\.1' /etc/hosts
printf '127.0.1.1\tsrv01.example.com\tsrv01\n' >> /etc/hosts

If the first command already prints a line, edit that one instead of appending a second. With two lines for the same address the first one wins, and you would go on to edit a line that nobody reads any more.

Verification:

getent hosts srv01
getent hosts srv01.example.com
hostname -f
hostname -s

The two getent calls have to return one line each, hostname -f the fully qualified name and hostname -s the short one. If nothing comes back, the entry is not in effect, for example because the line carries a comment character.

One decision is left: 127.0.1.1 or the public address of the server. Some software binds to the address that its own name resolves to, or registers that address in the member list of a cluster. In that case the FQDN belongs on the public address. With a fixed address that is the cleaner variant, with a changing address it is 127.0.1.1, because that one also works without a network connection.

Why a missing entry makes sudo slow

sudo determines the machine name on every call and has it resolved. It needs the name for the log line and for matching against the host entries in /etc/sudoers.

Resolution follows the hosts: line from /etc/nsswitch.conf. If the name is in /etc/hosts, the matter is settled after one file access, so in well under a millisecond. If it is not there, the query moves on to the next entry, usually dns, and the resolver asks the nameservers from /etc/resolv.conf for a name that does not exist in DNS. The glibc defaults are a five second timeout and two attempts per nameserver. If nobody answers, that adds up, and it does so on every single call.

One thing makes it worse: a short name contains no dot and therefore falls below the default ndots:1. The resolver first appends every search domain from /etc/resolv.conf and only queries the bare name afterwards. Two search domains mean three query rounds instead of one.

You can see it in this warning, which is printed before every call:

sudo: unable to resolve host srv01: Name or service not known

Measure instead of guessing:

time getent hosts "$(hostname)"
time sudo -n true

Both should stay well below a tenth of a second. Anything above that is time spent waiting for a nameserver.

And now the reason why the fault does not stand out everywhere: if the hosts: line contains the entry myhostname, that module answers the query for the machine name itself, without DNS and without /etc/hosts. There the warning never appears, even though the file is incomplete. As soon as the same setup moves to a system without that entry, the fault is back.

A second, rarer problem concerns permissions: in /etc/sudoers, every rule can be restricted to specific machine names. The rules that Debian and Ubuntu ship use ALL and are harmless. Your own rules that name a machine, on the other hand, lose their effect, and the user in question is then allowed to do nothing at all. Check beforehand:

grep -rhvE '^[[:space:]]*(#|$)' /etc/sudoers /etc/sudoers.d/

Services that read the name at startup

Many programs query the hostname exactly once, at startup, and carry on with the old name afterwards. That explains a good part of the confusion after a rename.

  • The running shell. The prompt shows the old name. Open a new session and you are done.
  • rsyslog, where it is installed. A systemctl restart rsyslog is enough. Minimal installations of Debian 12 and 13 do not ship rsyslog, journald does the writing there.
  • Log entries that have already been written keep the old name, and that is as it should be. If the old name turns up in new entries, restart the service that writes them.
  • MariaDB and MySQL derive default file names such as the error log and the binary log from the hostname, unless the paths are stated explicitly in the configuration.
  • Java applications. InetAddress.getLocalHost() throws a java.net.UnknownHostException as soon as the machine name fails to resolve. Application servers are affected just as much as game servers.
  • Monitoring agents often carry the name in their own configuration file. Otherwise the same server turns up twice after the rename.
systemctl list-units --type=service --state=running

If a maintenance window is coming up anyway, a reboot is the most complete solution. How to register your own programs properly as a unit, so that they survive a reboot, is covered in Creating a systemd service.

Mail server: the name that others see

When mail is sent, the hostname stops being cosmetic. The remote end sees it in the EHLO and checks it. Three things have to fit together:

  1. The name your mail server announces itself with (in Postfix, myhostname).
  2. The PTR record of your IP address, that is the reverse lookup.
  3. An A record (with IPv6, an AAAA record) for exactly that name, pointing back to the same address.

If the chain does not close, many recipients downgrade the message or reject it. Checking without additional packages:

hostname -f
getent hosts 203.0.113.10
getent hosts srv01.example.com

You get more detail with dig from the bind9-dnsutils package:

apt install -y bind9-dnsutils
dig +short -x 203.0.113.10
dig +short srv01.example.com A

The PTR record does not belong to the server. It hangs off the IP address and is maintained by the operator of the network, at KernelHost through the customer panel. No hostnamectl call changes anything about it, and this is exactly where renames go wrong on mail servers.

Postfix does not pick up the rename on its own. On Debian and Ubuntu the package writes a fixed value into /etc/postfix/main.cf during setup, and next to it sits /etc/mailname with the name that Postfix uses as the sender domain for local mail. Both stay exactly as they were:

postconf myhostname mydomain myorigin
cat /etc/mailname

Adjust and restart, keeping in mind that /etc/mailname is a decision of its own and does not necessarily carry the same value as myhostname:

postconf -e "myhostname = srv01.example.com"
postfix check
systemctl restart postfix

SPF, DKIM and DMARC, by contrast, hang off the sender domain and not off the hostname. So a rename does not fix any delivery problem that has its cause there.

Certificates

The system name appears in no certificate, because a certificate covers the DNS names that were in the request. Even so, a rename makes itself felt in three places.

First, in the request. If you get a certificate for the server name itself, for the mail server for example, the name has to be in DNS before the certificate authority looks it up. Otherwise the run ends with a message such as DNS problem: NXDOMAIN looking up A for srv01.example.com. The A record belongs before the request, not after it.

Second, with the certificates you already have. They keep running on the old name and keep being renewed until you remove them:

certbot certificates
certbot delete --cert-name old.example.com

Only delete once no service points at the path any more, otherwise the web server will not start on its next reload.

Third, at the mail server. If Postfix announces itself with the new name but presents a certificate for the old one, remote systems that check the name strictly will fail. The certificate and myhostname belong on the same name.

There is no connection between the system name and server_name in nginx. nginx decides on the basis of the Host header of the request, not on the basis of the machine name. If you get the wrong site served after a rename, look in the server configuration, not at the hostname. The basics on that are in Installing nginx on Debian and Ubuntu.

Common errors and fixes

sudo: unable to resolve host srv01: Name or service not known
The machine name is not in /etc/hosts and is unknown in DNS. Add the line 127.0.1.1 srv01.example.com srv01. As long as it is missing, every call waits for a resolver timeout.

hostname: Name or service not known
The answer from hostname -f when the kernel name cannot be resolved anywhere. Same cause, same fix. Afterwards check with getent hosts "$(hostname)" whether something really comes back.

Could not set property: Access denied
hostnamectl was called without root privileges, or the call ran in a container that is not allowed to change the kernel name. On your own server, sudo helps. In an unprivileged container you set the name in its configuration, not inside it.

fatal: unable to use my own hostname
From the Postfix log. The value in myhostname does not resolve. Add the entry to /etc/hosts, then run systemctl restart postfix.

504 5.5.2 <srv01>: Helo command rejected: need fully-qualified hostname
Your mail server announces itself with the short name, and the remote end demands a fully qualified one. Set myhostname to the FQDN and restart Postfix.

450 4.7.1 Client host rejected: cannot find your reverse hostname
There is no PTR record for your IP address, or it points nowhere. This cannot be fixed on the server, only by the operator of the IP network.

java.net.UnknownHostException: srv01
A Java application tried to resolve its own name and failed. /etc/hosts again. Once the entry is in place, the application has to be restarted.

DNS problem: NXDOMAIN looking up A for srv01.example.com
The new name does not exist in DNS yet. Create the A record, wait for it to propagate, repeat the request.

The name is back to the old one after the reboot.
Check in this order: is preserve_hostname: true set under /etc/cloud/cloud.cfg.d/, does the DHCP client no longer deliver a name, does /etc/hostname actually contain the new name. If hostnamectl shows a Transient hostname: line after booting, one of these sources is still at work.

/etc/hosts is wiped clean again after every reboot.
manage_etc_hosts: true is set, and cloud-init regenerates the file from the template. Switch it to localhost or maintain the template.

Differences between distributions

Systemcloud-init out of the boxrsyslogNotable difference
Debian 13 (trixie)only on cloud imagesmissing in minimal installationslogs in the journal instead of in /var/log/syslog
Debian 12 (bookworm)only on cloud imagesdepends on the installation variantsame as Debian 13
Ubuntu 24.04 LTSyes, preserve_hostname: falsepresentsystemd-resolved active, resolve appears in the hosts: line
Ubuntu 22.04 LTSyes, preserve_hostname: falsepresentsame as Ubuntu 24.04

The same on all four systems: hostnamectl never changes /etc/hosts, and no tool of the operating system creates DNS or PTR records. Those two steps stay manual work.

The final check

A command without an error message is no proof. The only reliable test is the reboot, followed by these five lines:

hostnamectl --static
hostname -f
getent hosts "$(hostname)"
time sudo -n true
grep -c '^127\.0\.1\.1' /etc/hosts

What you expect: the new short name, the new fully qualified name, one line from /etc/hosts, a runtime well under a tenth of a second, and exactly one line for 127.0.1.1. Only when all five are correct is the rename complete, and only then may you close the second terminal window.

Frequently asked questions

Why is my hostname back to the old one after a reboot?
Because something else sets it at boot, as a rule cloud-init or the DHCP client. Create the file /etc/cloud/cloud.cfg.d/99_hostname.cfg with the line preserve_hostname: true, and cloud-init leaves the name alone. For the DHCP client, netplan offers the setting use-hostname: false in dhcp4-overrides, and systemd-networkd has UseHostname=no in the [DHCPv4] section. You can tell from the output of hostnamectl whether anything has a say at all: if a Transient hostname line appears next to Static hostname, the stored name and the running name differ.
Why does sudo report "unable to resolve host" and suddenly need seconds?
sudo has the machine name resolved on every call, for the log line and for matching against the host entries in /etc/sudoers. If the name is not in /etc/hosts, the query goes on to the DNS resolver, and with the glibc defaults that resolver waits five seconds per attempt, at two attempts per nameserver. A short name without a dot also falls below ndots:1, which is why every search domain is appended first. The line 127.0.1.1 srv01.example.com srv01 in /etc/hosts ends this immediately.
Does /etc/hostname take the short name or the fully qualified name?
Both work. On Debian and Ubuntu the short name is the convention, and the fully qualified name is then the first entry in the matching line of /etc/hosts. That keeps the prompt and the log lines short, and hostname -f still returns the FQDN. Many cloud images write the FQDN into /etc/hostname instead, which is just as clean. The actual mistake is mixing the two: one fully qualified name in /etc/hostname and a different one in /etc/hosts.
Why 127.0.1.1 in /etc/hosts and not 127.0.0.1?
Debian and Ubuntu deliberately keep the machine name separate from localhost. The first name after an address is the canonical one, and that is exactly what hostname -f returns. If you append the server name as an alias to the localhost line, localhost stays the canonical name, and hostname -f answers with localhost. Programs that derive their own name from it then write that into log files and email headers. A separate line with 127.0.1.1 avoids this. With a fixed public address you can also put the fully qualified name directly on that address.
Does hostnamectl change /etc/hosts as well?
No. hostnamectl writes /etc/hostname, sets the running kernel name and, on request, maintains /etc/machine-info. It never touches the file /etc/hosts. The only thing that changes that file automatically is cloud-init with the manage_etc_hosts setting: the value true regenerates the file from a template at every boot, the value localhost only makes sure that the machine name resolves, and without the setting the file stays untouched.
Is the hostname command enough for a permanent change?
No. hostname srv01 only sets the transient name in the kernel, and it is gone after the next reboot, because the name is then loaded from /etc/hostname again. For a permanent change use hostnamectl set-hostname srv01, which sets the static and the transient name together. Newer systemd versions also know the short form hostnamectl hostname srv01.
What else do I have to adjust for my mail server?
Three things have to fit together: the name in the EHLO (in Postfix, myhostname), the PTR record of your IP address and an A record, or AAAA record, for exactly that name, pointing back to the same address. Postfix does not pick up the rename on its own, because on Debian and Ubuntu the package writes a fixed value into /etc/postfix/main.cf, and /etc/mailname sits next to it. The PTR is not set on the server but by the operator of the IP network, at KernelHost through the customer panel. If it is missing, strict recipients reject with 450 4.7.1 Client host rejected: cannot find your reverse hostname.

Hostname hostnamectl cloud-init Linux Debian Ubuntu DNS Root Server