How do I show active dhcp leases

  • Not experiened with Ubuntu. Set up a dhcp server about a year ago but don't remember the command to see the active leases.

  • Try checking the lease file /var/lib/dhcp/dhcpd.leases

  • Our organization uses a Python script, as posted below, to examine the /var/lib/dhcp/dhcpd.leases file:

    import datetime, bisect
    def parse_timestamp(raw_str):
            tokens = raw_str.split()
            if len(tokens) == 1:
                    if tokens[0].lower() == 'never':
                            return 'never';
                            raise Exception('Parse error in timestamp')
            elif len(tokens) == 3:
                    return datetime.datetime.strptime(' '.join(tokens[1:]),
                            '%Y/%m/%d %H:%M:%S')
                    raise Exception('Parse error in timestamp')
    def timestamp_is_ge(t1, t2):
            if t1 == 'never':
                    return True
            elif t2 == 'never':
                    return False
                    return t1 >= t2
    def timestamp_is_lt(t1, t2):
            if t1 == 'never':
                    return False
            elif t2 == 'never':
                    return t1 != 'never'
                    return t1 < t2
    def timestamp_is_between(t, tstart, tend):
            return timestamp_is_ge(t, tstart) and timestamp_is_lt(t, tend)
    def parse_hardware(raw_str):
            tokens = raw_str.split()
            if len(tokens) == 2:
                    return tokens[1]
                    raise Exception('Parse error in hardware')
    def strip_endquotes(raw_str):
            return raw_str.strip('"')
    def identity(raw_str):
            return raw_str
    def parse_binding_state(raw_str):
            tokens = raw_str.split()
            if len(tokens) == 2:
                    return tokens[1]
                    raise Exception('Parse error in binding state')
    def parse_next_binding_state(raw_str):
            tokens = raw_str.split()
            if len(tokens) == 3:
                    return tokens[2]
                    raise Exception('Parse error in next binding state')
    def parse_rewind_binding_state(raw_str):
            tokens = raw_str.split()
            if len(tokens) == 3:
                    return tokens[2]
                    raise Exception('Parse error in next binding state')
    def parse_leases_file(leases_file):
            valid_keys = {
                    'starts':               parse_timestamp,
                    'ends':                 parse_timestamp,
                    'tstp':                 parse_timestamp,
                    'tsfp':                 parse_timestamp,
                    'atsfp':                parse_timestamp,
                    'cltt':                 parse_timestamp,
                    'hardware':             parse_hardware,
                    'binding':              parse_binding_state,
                    'next':                 parse_next_binding_state,
                    'rewind':               parse_rewind_binding_state,
                    'uid':                  strip_endquotes,
                    'client-hostname':      strip_endquotes,
                    'option':               identity,
                    'set':                  identity,
                    'on':                   identity,
                    'abandoned':            None,
                    'bootp':                None,
                    'reserved':             None,
            leases_db = {}
            lease_rec = {}
            in_lease = False
            in_failover = False
            for line in leases_file:
                    if line.lstrip().startswith('#'):
                    tokens = line.split()
                    if len(tokens) == 0:
                    key = tokens[0].lower()
                    if key == 'lease':
                            if not in_lease:
                                    ip_address = tokens[1]
                                    lease_rec = {'ip_address' : ip_address}
                                    in_lease = True
                                    raise Exception('Parse error in leases file')
                    elif key == 'failover':
                            in_failover = True
                    elif key == '}':
                            if in_lease:
                                    for k in valid_keys:
                                            if callable(valid_keys[k]):
                                                    lease_rec[k] = lease_rec.get(k, '')
                                                    lease_rec[k] = False
                                    ip_address = lease_rec['ip_address']
                                    if ip_address in leases_db:
                                            leases_db[ip_address].insert(0, lease_rec)
                                            leases_db[ip_address] = [lease_rec]
                                    lease_rec = {}
                                    in_lease = False
                            elif in_failover:
                                    in_failover = False
                                    raise Exception('Parse error in leases file')
                    elif key in valid_keys:
                            if in_lease:
                                    value = line[(line.index(key) + len(key)):]
                                    value = value.strip().rstrip(';').rstrip()
                                    if callable(valid_keys[key]):
                                            lease_rec[key] = valid_keys[key](value)
                                            lease_rec[key] = True
                                    raise Exception('Parse error in leases file')
                            if in_lease:
                                    raise Exception('Parse error in leases file')
            if in_lease:
                    raise Exception('Parse error in leases file')
            return leases_db
    def round_timedelta(tdelta):
            return datetime.timedelta(tdelta.days,
                    tdelta.seconds + (0 if tdelta.microseconds < 500000 else 1))
    def timestamp_now():
            n = datetime.datetime.utcnow()
            return datetime.datetime(n.year, n.month,, n.hour, n.minute,
                    n.second + (0 if n.microsecond < 500000 else 1))
    def lease_is_active(lease_rec, as_of_ts):
            return timestamp_is_between(as_of_ts, lease_rec['starts'],
    def ipv4_to_int(ipv4_addr):
            parts = ipv4_addr.split('.')
            return (int(parts[0]) << 24) + (int(parts[1]) << 16) + \
                    (int(parts[2]) << 8) + int(parts[3])
    def select_active_leases(leases_db, as_of_ts):
            retarray = []
            sortedarray = []
            for ip_address in leases_db:
                    lease_rec = leases_db[ip_address][0]
                    if lease_is_active(lease_rec, as_of_ts):
                            ip_as_int = ipv4_to_int(ip_address)
                            insertpos = bisect.bisect(sortedarray, ip_as_int)
                            sortedarray.insert(insertpos, ip_as_int)
                            retarray.insert(insertpos, lease_rec)
            return retarray
    myfile = open('/var/lib/dhcp/dhcpd.leases', 'r')
    leases = parse_leases_file(myfile)
    now = timestamp_now()
    report_dataset = select_active_leases(leases, now)
    print('| IP Address      | MAC Address       | Expires (days,H:M:S) | Client Hostname ')
    for lease in report_dataset:
            print('| ' + format(lease['ip_address'], '<15') + ' | ' + \
                    format(lease['hardware'], '<17') + ' | ' + \
                    format(str((lease['ends'] - now) if lease['ends'] != 'never' else 'never'), '>20') + ' | ' + \
    print('| Total Active Leases: ' + str(len(report_dataset)))
    print('| Report generated (UTC): ' + str(now))

    I like this, as it filters out previously expired leases (unlike viewing /var/lib/dhcp/dhcpd.leases). Thanks for sharing.

    No problem, tell your friends

    Thanks Luke. I shared a link to this page on the Facebook WelcomeToLinux page.

  • If you are using NetworkManager (which is default in many distributions) the .lease files is located in /var/lib/NetworkManager

    $ sudo ls -al /var/lib/NetworkManager/*.lease
    -rw-r--r-- 1 root root 399 Jun 12 10:23 /var/lib/NetworkManager/
    -rw-r--r-- 1 root root 856 Jun 12 10:30 /var/lib/NetworkManager/
    -rw-r--r-- 1 root root 800 Jun 12 10:30 /var/lib/NetworkManager/

    Not sure if this is because of Desktop vs Server or version change but this is the correct location for Ubuntu Desktop 14.04, it was not in /var/lib/dhcp/dhcpd.leases as other answers have suggested.

  • Here's a great command using CLI - You can go to the directory where dhcpd.log file is located and do:

    tail -f dhcpd.log

    That will show you leases as they are being issued by the server in real time.

    You can also do:

    cat /var/lib/dhcpd/dhcpd.leases to see leases that are in the lease file dhcpd.leases

  • I use this script:

    my $VERSION=0.03;
    my $leases_file = "/var/lib/dhcp/dhcpd.leases";
    use strict;
    use Date::Parse;
    my $now = time;
    my %seen;       # leases file has dupes (because logging failover stuff?). This hash will get rid of them.
    open(L, $leases_file) or die "Cant open $leases_file : $!\n";
    undef $/;
    my @records = split /^lease\s+([\d\.]+)\s*\{/m, <L>;
    shift @records; # remove stuff before first "lease" block
    ## process 2 array elements at a time: ip and data
    foreach my $i (0 .. $#records) {
        next if $i % 2;
        my $ip;
        ($ip, $_) = @records[$i, $i+1];
        s/^\n+//;     # && warn "leading spaces removed\n";
        s/[\s\}]+$//; # && warn "trailing junk removed\n";
        my ($s) = /^\s* starts \s+ \d+ \s+ (.*?);/xm;
        my ($e) = /^\s* ends   \s+ \d+ \s+ (.*?);/xm;
        my $start = str2time($s);
        my $end   = str2time($e);
        my %h; # to hold values we want
        foreach my $rx ('binding', 'hardware', 'client-hostname') {
            my ($val) = /^\s*$rx.*?(\S+);/sm;
            $h{$rx} = $val;
        my $formatted_output;
        if ($end && $end < $now) {
            $formatted_output =
                sprintf "%-15s : %-26s "              . "%19s "         . "%9s "     . "%24s    "              . "%24s\n",
                        $ip,     $h{'client-hostname'}, ""              , $h{binding}, "expired"               , scalar(localtime $end);
        else {
            $formatted_output =
                sprintf "%-15s : %-26s "              . "%19s "         . "%9s "     . "%24s -- "              . "%24s\n",
                        $ip,     $h{'client-hostname'}, "($h{hardware})", $h{binding}, scalar(localtime $start), scalar(localtime $end);
        next if $seen{$formatted_output};
        print $formatted_output;

    You may want to adapt it to suit your needs.

    There are also Perl modules which you may want to try if you have a vague notion of Perl: Net::ISC::DHCPd::Leases, POE::Filter::DHCPd::Lease or Text::DHCPLeases.

    The last one can be installed with

    sudo apt-get install libtext-dhcpleases-perl

    The others with cpan -i.

    Unfortunately, I haven't tried any of them, because I already had my script when I noticed them.

    I wanted to try your Perl script but got an error. Can't use global $_ in "my" at ./bin/ line 22, near ", $_"

    @LinuxGuru: That is just a warning, and the script still works (at least for me). But anyway, I changed it to remove that warning.

  • Try enter this command:


    This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review

    @Pilot6 **if** OP uses ISC DHCPD this is actually correct - see `man (8) dhcp-lease-list` .

    @guntbert It may be correct. But it is not an answer without any explanation.

  • If you want something in a GUI, take a look at Glass. It runs as a web-app, and provides access to your DHCPd config file, as well as the leases. It uses graphs and stats, which are helpful if you have multiple subnets or pools.

    I also like that it alerts me if activity seems off - devices not getting addresses en masse, or too many requests from single clients. I've been using it for a month now, and I love it.

License under CC-BY-SA with attribution

Content dated before 6/26/2020 9:53 AM