I have a need to point devices to sync their clocks to my CentOS-7 server running inside LXC. On the server I'm trying to use ntpd from ntp package, but open to other products. This question is about the setting up the ntpd or equivalent on the server.

So far I tried this in /etc/ntp.conf

driftfile /var/lib/ntp/drift
restrict default nomodify notrap nopeer noquery

restrict ::1

server iburst
fudge stratum 8

disable monitor

There are TWO problem here.

  1. ntpd terminates after logging cap_set_proc() failed to drop root privileges: Operation not permitted
  2. ntpd is trying to adjust the local time. It fails, but it tries. If this was the only problem and I had error message in the log I could accept that.

The full output from /var/log/messages caused by attempt to start ntpd:

systemd: Starting Network Time Service...
ntpd[20154]: ntpd [email protected] Wed Apr 12 21:24:06 UTC 2017 (1)
ntpd[20155]: proto: precision = 0.120 usec
ntpd[20155]: ntp_adjtime() failed: Operation not permitted
systemd: Started Network Time Service.
ntpd[20155]: c01d 0d kern kernel time sync enabled
ntpd[20155]: Listen and drop on 0 v4wildcard UDP 123
ntpd[20155]: Listen and drop on 1 v6wildcard :: UDP 123
ntpd[20155]: Listen normally on 2 lo UDP 123
ntpd[20155]: Listen normally on 3 eth0 hidden:A.B.C.D UDP 123
ntpd[20155]: Listen normally on 4 tun0 hidden:E.F.G.H UDP 123
ntpd[20155]: Listening on routing socket on fd #21 for interface updates
ntpd[20155]: c016 06 restart
ntpd[20155]: ntp_adjtime() failed: Operation not permitted
ntpd[20155]: c012 02 freq_set kernel 0.000 PPM
ntpd[20155]: c011 01 freq_not_set
ntpd[20155]: cap_set_proc() failed to drop root privileges: Operation not permitted
systemd: ntpd.service: main process exited, code=exited, status=255/n/a
systemd: Unit ntpd.service entered failed state.
systemd: ntpd.service failed.

3 Answers 3


As discussed in comments, chrony recently received a new option -x to not attempt to alter the system clock, making it especially suitable for container operations. Alas, the first version (3.2) to receive this option wasn't thorough and still requests the Linux capability, so still fails.

Stracing chronyd from package chrony version 3.2-2.el7 in an CentOS7 LXC container (with non-CentOS host) with option -x, indeed the bugfix is not here:

# strace /usr/sbin/chronyd -x -d


[pid   571] capget({_LINUX_CAPABILITY_VERSION_3, 0}, NULL) = 0
[pid   571] capset({_LINUX_CAPABILITY_VERSION_3, 0}, {1<<CAP_NET_BIND_SERVICE|1<<CAP_SYS_TIME, 1<<CAP_NET_BIND_SERVICE|1<<CAP_SYS_TIME, 0}) = -1 EPERM (Operation not permitted)

So if you could prevent the unmodifiable binary chronyd to request a forbidden capability, it would run (that's what is the 3.3 bugfix about). Good news, it's possible with a LD_PRELOAD/dlsym() wrapper.

Compile on an other Linux system elsewhere (the compilation was actually made on the Debian 9 host and it didn't give any trouble to run on the CentOS7 container as is) this code called capsetwrapper.c, with structures definitions found there for example (this didn't change from kernel 3.10 either).

#define _GNU_SOURCE 1
#include <dlfcn.h>

#include <sys/capability.h>

int capset(cap_user_header_t hdrp, const cap_user_data_t datap) {
    int (*orig_capset)(cap_user_header_t,const cap_user_data_t)=dlsym(RTLD_NEXT,"capset");
    datap->effective &= ~ (1<<CAP_SYS_TIME);
    datap->permitted &= ~ (1<<CAP_SYS_TIME);
    return orig_capset(hdrp, datap);

using this specific way (to make a library suitable for LD_PRELOAD usage):

gcc -shared -fPIC -o libcapsetwrapper.so capsetwrapper.c -ldl

And it's working as seen here:

[root@centos7-amd64bis ~]# LD_PRELOAD=/root/libcapsetwrapper.so /usr/sbin/chronyd -x -d
2019-03-24T10:09:58Z chronyd version 3.2 starting (+CMDMON +NTP +REFCLOCK +RTC +PRIVDROP +SCFILTER +SECHASH +SIGND +ASYNCDNS +IPV6 +DEBUG)
2019-03-24T10:09:58Z Disabled control of system clock
2019-03-24T10:09:58Z Frequency 0.000 +/- 1000000.000 ppm read from /var/lib/chrony/drift

checking its capabilities while running:

# egrep '^Cap(Prm|Eff)' /proc/$(pidof chronyd)/status
CapPrm: 0000000000000400
CapEff: 0000000000000400

shows 0x400 which is the remaining 1<<CAP_NET_BIND_SERVICE as seen above.

To integrate this in the system:

  • place the libcapsetwrapper.so wrapper as /usr/local/lib/libcapsetwrapper.so

  • with systemctl edit chronyd, override the CAP_SYS_TIME check and the executable start with this:

    ExecStart=/bin/sh -c 'export LD_PRELOAD=/usr/local/lib/libcapsetwrapper.so; exec /usr/sbin/chronyd -x'

    Sorry I wasn't able to reuse the $OPTIONS parameter (which is empty and should be reeiving the -x option, loaded from /etc/sysconfig/chronyd), but with more systemd knowledge it should be possible.

Working result:

# systemctl status chronyd
● chronyd.service - NTP client/server
   Loaded: loaded (/usr/lib/systemd/system/chronyd.service; enabled; vendor preset: enabled)
  Drop-In: /etc/systemd/system/chronyd.service.d
   Active: active (running) since Sun 2019-03-24 10:24:26 UTC; 13min ago
     Docs: man:chronyd(8)
  Process: 843 ExecStartPost=/usr/libexec/chrony-helper update-daemon (code=exited, status=0/SUCCESS)
  Process: 839 ExecStart=/bin/sh -c export LD_PRELOAD=/usr/local/lib/libcapsetwrapper.so; exec /usr/sbin/chronyd -x (code=exited, status=0/SUCCESS)
 Main PID: 841 (chronyd)
   CGroup: /system.slice/chronyd.service
           `-841 /usr/sbin/chronyd -x

What wasn't tested is if the default SELinux environment (not available here) allows the preload operation or if something more should be done with /usr/local/lib/libcapsetwrapper.so (be sure to use restorecon on it).


Since version 7.7.1908 CentOS has chrony version 3.4 which supports the -x flag correctly and can run under LXC, with minor configuration tweaks.

  • Default /usr/lib/systemd/system/chronyd.service has ConditionCapability=CAP_SYS_TIME which should not be there when run with -x under LXC, so remove it by adding this drop in file /etc/systemd/system/chronyd.service.d/lxc.conf
    # Default service has:
    # ConditionCapability=CAP_SYS_TIME
    # which does not work under LXC and causes service to refuse to start
    # Clear it.
  • Edit /etc/sysconfig/chronyd and add -x to OPTIONS to end up with OPTIONS="-x"

  • Change /etc/chrony.conf to NOT sync to any NTP servers but to serve time from the local clock. Remove/disable all other server entries, and add:

    # Comment out default/other server entries
    #server 0.centos.pool.ntp.org iburst
    #server 1.centos.pool.ntp.org iburst
    #server 2.centos.pool.ntp.org iburst
    #server 3.centos.pool.ntp.org iburst
    # Add these as per https://www.thegeekdiary.com/centos-rhel-7-chrony-how-to-sync-to-local-clock/
    local stratum 10
    allow all

Review the allow directive to suit your needs because allow all as in the example might not be right for you.

  • Reload and restart the service
    systemctl daemon-reload
    systemctl restart chronyd

This should be it.

  • The content of this answer was proposed as an edit to complement A.B's earlier answer. Unfortunately the edit was rejected so now instead of one complete answer we have another partial answer that references another partial answer.
    – AnyDev
    Jul 15, 2020 at 14:34
  • I only addressed the "doesn't start" part which isn't required anymore beside -x, you addressed the specific NTP config part. +1
    – A.B
    Aug 29, 2020 at 14:43

I found a way to get it working but I'm not happy with my solution, so I'm still looking for a better answer.

Part 1. I prevented ntpd from trying to drop root privileges.

In /usr/lib/systemd/system/ntpd.service it has by default:

ExecStart=/usr/sbin/ntpd -u ntp:ntp $OPTIONS

so I ''remove'' -u ntp:ntp by placing /etc/systemd/system/ntpd.service.d/local.conf

ExecStart=/usr/sbin/ntpd $OPTIONS

Which allows ntpd to keep running as root. It works, but I'm not happy with this approach.

Part 2. The not updating local time part

Adding in /etc/ntp.conf line disable kernel causes ntpd at the service start to log error ntp_adjtime() failed: Operation not permitted only once as opposed to 3 times without the setting. Since it can not change the clock this is acceptable. Ideally it would not even try to change the clock.

  • 1
    chrony version 3.2 (with bugfix in 3.3) inherited a -x flag to be running in containers (without time capability). Some adjustments to be done (eg on Debian buster disable systemd's ConditionCapability=CAP_SYS_TIME , add -x in default options and better comment "rtcsync" in config, and add some allow entry, since it won't answer requests by default for security)
    – A.B
    Feb 27, 2019 at 22:53
  • Thank you @A.B. This looks like it should be the answer, I just can't test yet. I'm stuck using binary only CentOS releases, which don't have chrony 3.3 yet, and I could not get 3.2 to work, seem to be hitting the bug. I'll have to revisit it, either when I have more time to tinker or 3.3 is avail.
    – AnyDev
    Mar 13, 2019 at 12:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .