Image of laptop with hand holding a skeleton key extending outwards through the display.

Configure Google Authenticator on CentOS 7


As part of the rebuild on my Plex Media Server using CentOS 7, I had intended to configure Google Authenticator but hadn’t gotten around to doing it yet.  As I got into the process recently I discovered that many of the steps that I had used when configuring my CentOS 6 Digital Ocean droplet were out of date to the point of uselessness.

I also discovered that most of the guides that I found either relied on the older 1.0 code release which was also outdated or used a unknown RPM repo.  As such I decided to write up the process that I followed to use the code downloaded from the official GitHub repository.

NOTE: If you are doing this in an enterprise setting, it is likely that your company has particular settings and restrictions that you may need to adhere to (e.g., not running things as the root user). Also, please note that all of my examples use the CentOS defaults unless specifically noted.

Install the pre-requisites

As I have started with a minimal CentOS 7 install (since I don’t have any need for a GUI or the other extras) many of the packages I needed were not part of the baseline install set.  Here is the list of additional packages that I installed (note that this will get quite a few items to satisfy numerous dependencies)

  • autoconf
  • automake
  • bind-utils
  • gcc
  • libtool
  • make
  • nmap-netcat
  • ntp
  • pam-devel
  • unzip
  • wget

[code language=”bash”]
[root@server ~]# yum -y install autoconf automake bind-utils gcc libtool make nmap-netcat ntp pam-devel unzip wget



Configure and test NTP

An essential part of the 2FA system is an accurate clock.  This is because at it’s heart the Google Authenticator system is a Time-based One-time Password Algorithm (TOTP).  If you have too much skew between the clock on the server and the clock on the client, then your codes will fail intermittently.

Test NTP pool DNS resolution

First you need make sure that the name used by the NTP configuration file for the server will resolve to an IP address.

CentOS 7 uses the following as the NTP server pool set:


[code language=”bash”]
[root@server ~]# nslookup

Non-authoritative answer:

Test Network connectivity

Next up you should test that you actually have baseline connectivity to the NTP endpoint.  In general this should just work, like the DNS resolution did, however if you are running your server behind an IPS or a network firewall, it is possible that you will need to explicitly allow outbound NTP connections over port 53/UDP.

Please note that in the following code snippet, a result of zero (0) indicates a successful connection, whereas a result of one (1) indicates a failure to connect to the endpoint.

[code language=”bash”]
[root@server ~]# echo | nc -u -w1 53 >/dev/null 2>&1 ;echo $?

Test basic NTP functionality

Now that you have tested (and resolved any issues you found) your DNS resolution and baseline connectivity to the NTP endpoint you need to perform an actual application layer test.

[code language=”bash”]
[root@server ~]# ntpdate -q
server, stratum 2, offset -0.006191, delay 0.10498
server, stratum 2, offset -0.001065, delay 0.11761
server, stratum 3, offset -0.005018, delay 0.06509
server, stratum 2, offset -0.000588, delay 0.09003
28 Aug 17:07:05 ntpdate[16085]: adjust time server offset -0.000588 sec

Configure NTP daemon for startup

If you get a complete test, the next step will be to configure the NTP daemon to startup at system boot and to start the service.

[code language=”bash”]
[root@server ~]# systemctl enable ntpd
Created symlink from /etc/systemd/system/ to /usr/lib/systemd/system/ntpd.service.
[root@server ~]# systemctl start ntpd
[root@server ~]# systemctl status ntpd
● ntpd.service – Network Time Service
Loaded: loaded (/usr/lib/systemd/system/ntpd.service; enabled; vendor preset: disabled)
Active: active (running) since Sun 2016-08-28 17:08:33 EDT; 2h 34min ago
Process: 16150 ExecStart=/usr/sbin/ntpd -u ntp:ntp $OPTIONS (code=exited, status=0/SUCCESS)
Main PID: 16151 (ntpd)
CGroup: /system.slice/ntpd.service
└─16151 /usr/sbin/ntpd -u ntp:ntp -g

Aug 28 17:08:33 server ntpd[16151]: Listen normally on 2 lo UDP 123
Aug 28 17:08:33 server ntpd[16151]: Listen normally on 3 ens192 UDP 123
Aug 28 17:08:33 server ntpd[16151]: Listen normally on 4 ens192 fe80::20c:29ff:fe7e:af2f UDP 123
Aug 28 17:08:33 server ntpd[16151]: Listen normally on 5 lo ::1 UDP 123
Aug 28 17:08:33 server ntpd[16151]: Listen normally on 6 ens192 2601:901:8001:a070:20c:29ff:fe7e:af2f UDP 123
Aug 28 17:08:33 server ntpd[16151]: Listening on routing socket on fd #23 for interface updates
Aug 28 17:08:33 server systemd[1]: Started Network Time Service.
Aug 28 17:08:33 server ntpd[16151]: c016 06 restart
Aug 28 17:08:33 server ntpd[16151]: c012 02 freq_set kernel -115.767 PPM
Aug 28 17:08:34 server ntpd[16151]: c615 05 clock_sync[/code]

Build and Install Google Authenticator

Download the codebase

As I stated in the intro, many of the guides used an outdated codebase from the now defunct Google Code Project Hosting service or they used an RPM repository that I didn’t find completely trustworthy.  To resolve those problems, I decided to just rely on the GitHub repository that is maintained by the Google developer team.

The first step is to download the code.  The GitHub repository for the PAM module is located at

[code language=”bash”]
[root@server ~]# cd /opt
[root@server opt]# wget -q

Compile and install

NOTE: as part of the build and install process, I have included a directive to change the base directory that is used for the installation.  The reason for this is due to the need to create two symbolic links.  This will be covered in the next section.

The following are the steps as laid out in the PAM Module Instructions from the wiki:

make install

To anyone that has compiled from source, these are likely to be sufficient, however I have included an abbreviated version of my code snippet for this since it may provide additional information for those that aren’t quite so familiar with the process.

[code language=”bash”]
[root@server ~]# cd /opt/google-authenticator-master/libpam/
[root@server libpam]# ./
libtoolize: putting auxiliary files in AC_CONFIG_AUX_DIR, `build’.
libtoolize: copying file `build/’
libtoolize: putting macros in AC_CONFIG_MACRO_DIR, `build’.
libtoolize: copying file `build/libtool.m4′
libtoolize: copying file `build/ltoptions.m4′
libtoolize: copying file `build/ltsugar.m4′
libtoolize: copying file `build/ltversion.m4′
libtoolize: copying file `build/lt~obsolete.m4′ installing ‘build/config.guess’ installing ‘build/config.sub’ installing ‘build/install-sh’ installing ‘build/missing’ installing ‘build/depcomp’
parallel-tests: installing ‘build/test-driver’
[root@server libpam]# ./configure –prefix=/usr
checking for gcc… gcc
checking whether the C compiler works… yes
checking for C compiler default output file name… a.out
checking for suffix of executables…


google-authenticator version 1.01
Prefix………: /usr
Debug Build….:
C Compiler…..: gcc -std=gnu99 -g -O2
Linker………: /bin/ld -m elf_x86_64 -ldl
[root@server libpam]# make
make all-am
make[1]: Entering directory `/opt/google-authenticator-master/libpam’


/bin/sh ./libtool –tag=CC –mode=link gcc -std=gnu99 -g -O2 -o google-authenticator src/google-authenticator.o src/base32.o src/hmac.o src/sha1.o -ldl
libtool: link: gcc -std=gnu99 -g -O2 -o google-authenticator src/google-authenticator.o src/base32.o src/hmac.o src/sha1.o -ldl
make[1]: Leaving directory `/opt/google-authenticator-master/libpam’
[root@server libpam]# make install
make[1]: Entering directory `/opt/google-authenticator-master/libpam’
/bin/mkdir -p ‘/usr/bin’
/bin/sh ./libtool –mode=install /bin/install -c google-authenticator ‘/usr/bin’
libtool: install: /bin/install -c google-authenticator /usr/bin/google-authenticator
/bin/mkdir -p ‘/usr/share/doc/google-authenticator’
/bin/install -c -m 644 FILEFORMAT ‘/usr/share/doc/google-authenticator’
/bin/mkdir -p ‘/usr/share/doc/google-authenticator’
/bin/install -c -m 644 totp.html ‘/usr/share/doc/google-authenticator’
/bin/mkdir -p ‘/usr/lib/security’
/bin/sh ./libtool –mode=install /bin/install -c ‘/usr/lib/security’
libtool: install: /bin/install -c .libs/ /usr/lib/security/
libtool: install: /bin/install -c .libs/pam_google_authenticator.lai /usr/lib/security/
libtool: finish: PATH=”/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/sbin” ldconfig -n /usr/lib/security
Libraries have been installed in:

If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR’
flag during linking and do at least one of the following:
– add LIBDIR to the `LD_LIBRARY_PATH’ environment variable
during execution
– add LIBDIR to the `LD_RUN_PATH’ environment variable
during linking
– use the `-Wl,-rpath -Wl,LIBDIR’ linker flag
– have your system administrator add LIBDIR to `/etc/’

See any operating system documentation about shared libraries for
more information, such as the ld(1) and manual pages.
make[1]: Leaving directory `/opt/google-authenticator-master/libpam’

Configure PAM and SSH

The next two subsections cover the creation of two symbolic links (mentioned in the note in the previous section) and the change to the configuration file for the PAM system governing the SSH daemon.  Additionally, there are a couple of modifications that you might need to perform on the SSH daemon configuration as well, depending on your setup.

Add symlinks to 64bit libraries and module

During my installation, I ran into a situation that was caused by the location used for the installation of the PAM library and object files.  I solved this in two steps:

  1. Change the base location of the file installation
  2. Addition of two symbolic links

The first step was covered in the build process by the addition of a parameter to the call to the configure script:

[code language=”bash”]
[root@server libpam]# ./configure –prefix=/usr

The second step was accomplished using the following commands:

[code language=”bash”]
[root@server ~]# cd /usr/lib64/security/
[root@server security]# ln -s /usr/lib/security/
[root@server security]# ln -s /usr/lib/security/

As you can see from the following directory listing, this results in two symbolic links pointing back to the installed files in /usr/lib/security.

[code language=”bash”]
[root@server security]# pwd
[root@server security]# ls -l `find . -maxdepth 1 -type l -print`
lrwxrwxrwx 1 root root 45 Aug 28 17:58 ./ -> /usr/lib/security/
lrwxrwxrwx 1 root root 45 Aug 28 17:58 ./ -> /usr/lib/security/
lrwxrwxrwx. 1 root root 15 Jan 17 2016 ./ ->
lrwxrwxrwx. 1 root root 11 Jan 17 2016 ./ ->
lrwxrwxrwx. 1 root root 11 Jan 17 2016 ./ ->
lrwxrwxrwx. 1 root root 11 Jan 17 2016 ./ ->
lrwxrwxrwx. 1 root root 11 Jan 17 2016 ./ ->

Update PAM config file for SSH

After you have the files installed in the correct locations, you need to update the PAM configuration for SSH.  Now, there are a number of ways that you can go about doing this.  I chose to use an inline edit with sed.

NOTE: editing PAM configurations can result in denying access to your system if performed incorrectly.  When editing the SSH PAM configuration file, always do the following:

  1. BACKUP the original file
  2. Make sure you have console access
  3. Open a secondary SSH terminal so that if you screw up you are still logged in

[code language=”bash”]
[root@server ~]# cd /etc/pam.d/
[root@server pam.d]# cp -p sshd sshd_backup
[root@server pam.d]# sed -i “2iauth required” sshd

If you would like to use HOTP (counter based) instead of TOTP add the following the end of the text to be inserted using the sed command in the above command example:


Update the SSH configuration

In order to make the SSH part work there are three configuration settings that have to be set in a particular way:

  • PasswordAuthentication
  • UsePAM
  • ChallengeResponseAuthentication

The configuration file is located here:


The desired settings for these are as follows:

PasswordAuthentication yes
UsePAM yes
ChallengeResponseAuthentication yes

The first two are likely already set to be to the desired value, but the third one will need to be changed from “no” to “yes” on a default CentOS 7 install.  To check your settings run the following command and the edit the configuration file if needed:

[code language=”bash”]
[root@server ~]# egrep “PasswordAuthentication|UsePAM|ChallengeResponseAuthentication” /etc/ssh/sshd_config | egrep -v “#”
PasswordAuthentication yes
ChallengeResponseAuthentication yes
UsePAM yes

Next you need to restart the SSH daemon so that the changes take effect.  Warning: until you have configured the userland piece (see the next section) your SSH users won’t be able to login. (This includes the root user, but you aren’t logging in remotely as root anyway right?)

Configure userland

This step involves configuring the users to use a TOTP token by running the google-authenticator binary and then answering some questions.  Here is the usage statement for the binary:

google-authenticator [<options>]
 -h, --help Print this message
 -c, --counter-based Set up counter-based (HOTP) verification
 -t, --time-based Set up time-based (TOTP) verification
 -d, --disallow-reuse Disallow reuse of previously used TOTP tokens
 -D, --allow-reuse Allow reuse of previously used TOTP tokens
 -f, --force Write file without first confirming with user
 -l, --label=<label> Override the default label in "otpauth://" URL
 -i, --issuer=<issuer> Override the default issuer in "otpauth://" URL
 -q, --quiet Quiet mode
 -Q, --qr-mode={NONE,ANSI,UTF8}
 -r, --rate-limit=N Limit logins to N per every M seconds
 -R, --rate-time=M Limit logins to N per every M seconds
 -u, --no-rate-limit Disable rate-limiting
 -s, --secret=<file> Specify a non-standard file location
 -S, --step-size=S Set interval between token refreshes
 -w, --window-size=W Set window of concurrently valid codes
 -W, --minimal-window Disable window of concurrently valid codes

The following snippet shows the command output and answers of run of the google-authenticator binary for a regular user (NOTE: I have removed the actual emergency codes and the secret key, also if your terminal will display it, you may be presented with a QR code to scan.):

[code language=”bash”]
[root@server ~]# su – johndoe
[johndoe@server ~]$ google-authenticator

Do you want authentication tokens to be time-based (y/n) y
Your new secret key is: SECRET_KEY_HERE
Your verification code is VERIFY_CODE_HERE
Your emergency scratch codes are:

Do you want me to update your “/home/johndoe/.google_authenticator” file? (y/n) y

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y

By default, tokens are good for 30 seconds. In order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with
poor time synchronization, you can increase the window from its default
size of +-1min (window size of 3) to about +-4min (window size of
17 acceptable tokens).
Do you want to do so? (y/n) y

If the computer that you are logging into isn’t hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y

If you would like to just provide a single command for your users to run to avoid having to tell them the answers as laid out in the above example, use the following:

google-authenticator --time-based --disallow-reuse --window-size=17 --rate-limit=3 --rate-time=30 --force

Extra bits

The following sections detail two useful options.

Whitelist a network

It is possible that you may have a particular network segment, that by it’s very nature is secure enough to not require the additional steps of 2FA.  If this is the case for your installation, here is how you can whitelist a network segment.

  1. Create access control list
  2. Update /etc/pam.d/sshd to use to ACL

The following shows the contents of an ACL that would whitelist the network

[code language=”bash”]
[root@server ~]# vi /etc/security/2fa-acl.conf
[root@server ~]# cat /etc/security/2fa-acl.conf
# Allows certain network segments to skip 2FA
+ : ALL :
– : ALL : ALL

Once you have the ACL built add an exclusionary conditional to the SSH PAM configuration file BEFORE the line that was added to activate the module.  For example:

[code language=”bash”]
[root@server ~]# sed -i “2iauth [success=1 default=ignore] accessfile=/etc/security/2fa-acl.conf” /etc/pam.d/sshd

Allow unconfigured users

If you have a single user or two it will be easy enough to manage providing the string activate the google-authenticator system for a user, but if you are managing a server with a large number of users, you might want to establish a grace period during which users that have not yet configured their local account to continue to login.

This can be accomplished by adding this parameter to the end of the PAM configuration file line:


If you have already added the line and you just want to easily add the new parameter, then use the following sed command:

[code language=”bash”]
[root@server ~]# sed -i ‘2s/$/ nullok/’ /etc/pam.d/sshd


If you encounter login failures during your testing, a good place to start is the content of the following log files:

  • /var/log/secure
  • /var/log/audit/audit.log

Leave a Reply

%d bloggers like this: