Two factor authentication with OTP using privacyIDEA and FreeRADIUS on CentOS

In this howto we will show, how you can set up a the two factor authentication and management system privacyIDEA on Cent OS 6.5. privacyIDEA is a system that can manage authentication devices - especially OTP tokens of any kind.

We will set up the system to be served via Apache2, store the token information in a MySQL database and provide authentication via FreeRADIUS server, thus being able to add two factor authentication to all services accessible via RADIUS like SSL VPNs and pam_radius.


We need some special perl modules to run the connection between FreeRADIUS and privacyIDEA, which can be found in EPEL. So we need to install the EPEL repositories:

rpm -Uvh epel-release-6*.rpm

Install dependencies

Install the necessary packages:

yum install -y mysql-server httpd mod_wsgi mod_ssl python-devel gcc mysql-devel libjpeg-devel freeradius freeradius-utils freeradius-perl openldap-devel perl-libwww-perl perl-Config-IniFiles perl-Try-Tiny perl-Data-Dump python-virtualenv 

Configure the services to be started at bootup:

/sbin/chkconfig radiusd on 
/sbin/chkconfig mysqld on
/sbin/chkconfig httpd on

Create database

Now we create a database privacyidea that will hold the token data. We choose the database password unknown:

/etc/init.d/mysqld restart
echo 'create database privacyidea;' | mysql
echo 'grant all privileges on privacyidea.* to "privacyidea"@"localhost" identified by "unknown";' | mysql

Create the virtual python environment and install privacyIDEA

privacyIDEA will be installed to a virtualenv at /opt/privacyIDEA. Thus we can use all the python modules that are needed and can simply backup the complete folder.

virtualenv /opt/privacyIDEA 

Now we enter the virtual environment and install privacyIDEA:

cd /opt/privacyIDEA
source bin/activate
pip install privacyIDEA
pip install MySQL-python

Create config files

Still in the python virtualenv we create a service account and some config files:

mkdir -p /var/log/privacyidea
useradd -r privacyidea -d /opt/privacyIDEA

Create ini file

The ini file contains the configuration of the database and some other basic stuff. We copy the following file to /opt/privacyIDEA/etc/privacyidea/privacyidea.ini:

# privacyIDEA - Pylons development environment configuration
# The %(here)s variable will be replaced with the parent directory of this file
debug = false
profile = false
# Uncomment and replace with the address which should receive any error reports
#email_to = [email protected]
smtp_server = localhost
error_email_from = [email protected]

# default audit trail set to SQL Audit
privacyideaAudit.type = privacyidea.lib.auditmodules.sqlaudit
privacyideaAudit.key.private = %(here)s/private.pem
privacyideaAudit.key.public = %(here)s/public.pem
#privacyideaAudit.sql.url = mysql://privacyidea:[email protected]/privacyidea
#privacyideaAudit.sql.url = sqlite:///%(here)s/token.sqlite
# One entry for SQL audit might take about 1K
privacyideaAudit.sql.highwatermark = 10000
#privacyideaAudit.sql.lowwatermark = 5000
# If true, OTP values can be retrieved via the getotp controller = True
privacyideaSecretFile = %(here)s/encKey
# This file contains the token administrators. 
# It can be created like this:
# % tools/privacyidea-create-pwidresolver-user -u admin -p test -i 1000 >> config/admin-users
privacyideaSuperuserFile = %(here)s/admin-users
# list of realms, that are admins
privacyideaSuperuserRealms = superuser, 2ndsuperusers
privacyIDEASessionTimout = 1200
# This is the server, where this system is running.
# This is need to issue a request during login to the 
# management with an OTP token.
privacyideaURL = https://localhost
# This determines if the SSL certificate is checked during the login to 
# privacyIDEA. Set to True, if you have a self signed certificate.
privacyideaURL.disable_ssl = False
#privacyidea.useridresolver = privacyidea.lib.resolvers.PasswdIdResolver.IdResolver
# This is only used for testnig purposes for running the selftests.
#privacyidea.selfTest = True
# These are the settings for the RADIUS Token
# The location of the RADIUS dictionary file
radius.dictfile= %(here)s/dictionary
# The NAS Identifier of your privacyIDEA server, 
# that is sent to the RADIUS server
radius.nas_identifier = privacyIDEA
use = egg:Paste#http
#host =
host =
#host = localhost
port = 5001
#ssl_pem = *
use = egg:privacyIDEA
sqlalchemy.url = mysql://privacyidea:[email protected]/privacyidea
#sqlalchemy.url = sqlite:///%(here)s/token.sqlite
sqlalchemy.pool_recycle = 3600
full_stack = true
static_files = true
# We do not need a who.config, since we do the config in the
# code at config/
#who.config_file = %(here)s/who.ini
who.log_level = debug
who.log_file = /var/log/privacyidea/privacyidea.log

cache_dir = %(here)s/data
custom_templates = %(here)s/custom-templates/
#beaker.session.key = privacyidea
#beaker.session.secret = somesecret
# If you'd like to fine-tune the individual locations of the cache data dirs
# for the Cache data, or the Session saves, un-comment the desired settings
# here:
#beaker.cache.data_dir = %(here)s/data/cache
#beaker.session.data_dir = %(here)s/data/sessions

#  Note: You should change the Logging Level from DEGUB to WARN
# Logging configuration
keys = root, privacyidea, sqlalchemy
#keys = root, privacyidea, sqlalchemy, controllers
level = WARNING
handlers = file
level = INFO
handlers = file
qualname = privacyidea
#level = DEBUG
#handlers = file
#qualname = privacyidea.controllers.account
level = ERROR
handlers = file
qualname = sqlalchemy.engine
# "level = INFO" logs SQL queries.
# "level = DEBUG" logs SQL queries and results.
# "level = WARN" logs neither.  (Recommended for production systems.)
keys = file
class = handlers.RotatingFileHandler
# Make the logfiles 10 MB
# and rotate 4  files
#args = ('%(here)s/privacyidea.log','a', 10000000, 4)
# Please note, that the %(here)s parameter will not work, when
# running in wsgi.
args = ('/var/log/privacyidea/privacyidea.log','a', 10000000, 4)
level = INFO
formatter = generic
keys = generic
class = privacyidea.lib.log.SecureFormatter
format = %(asctime)s %(levelname)-5.5s {%(thread)d} [%(name)s][%(funcName)s #%(lineno)d] %(message)s
datefmt = %Y/%m/%d - %H:%M:%S

Create encryption key and signing keys

privacyidea-create-enckey -f /opt/privacyIDEA/etc/privacyidea/privacyidea.ini
privacyidea-create-auditkeys -f /opt/privacyIDEA/etc/privacyidea/privacyidea.ini

Create database tables

mkdir -p /var/log/privacyidea
paster setup-app /opt/privacyIDEA/etc/privacyidea/privacyidea.ini

Create admin user

Now we create the first admin user, that can log in to the privacyIDEA management:

privacyidea-create-pwidresolver-user -u admin -i 1000 >> /opt/privacyIDEA/etc/privacyidea/admin-users 

Enter a password and remember it.

Setup Apache

privacyIDEA is a python application that is run via the WSGI module. This modules needs an additional run directory. We create it:

mkdir -p /var/run/wsgi 

WSGI does not play well with SELinux. So for starters we need to disable the enforcing. In the file /etc/selinux/config we need to change this:


...and reboot the system to enable this.

After the reboot we again enter the virtualenv.

cd /opt/privacyIDEAy
source bin/activate

Apache config

In the apache directory /etc/httpd/conf.d we need to edit two files:

ssl.conf, which activates ssl:

LoadModule ssl_module modules/
Listen 443
SSLPassPhraseDialog  builtin
SSLSessionCache         shmcb:/var/cache/mod_ssl/scache(512000)
SSLSessionCacheTimeout  300
SSLMutex default
SSLRandomSeed startup file:/dev/urandom  256
SSLRandomSeed connect builtin
SSLCryptoDevice builtin

and privacyidea.conf, which is the configuration of the virtual host.

A script will help you to create the server certificate:

privacyidea-create-certificate -f /etc/httpd/conf.d/privacyidea.conf 

Fix access rights

During the setup process the files were generated for user root. But we will run privacyIDEA in Apache with the service account privacyidea. So we need to change the acccess rights of these files. A script helps us with this task:

privacyidea-fix-access-rights -f /opt/pirvacyIDEA/etc/privacyidea/privacyidea.ini -u privacyidea 

Then we need to restart the apache service:

service httpd restart 


The https port 443 might be closed. We can open it like this:

iptables -I INPUT 4 -p tcp --dport 443 -j ACCEPT
service iptables save

Now we can access the management UI via https on the server and create useridresolvers, a realm and enroll the first tokens. Another tutoral explains how to do this.

Setup FreeRADIUS

The privacyIDEA github repo contains a FreeRADIUS plugin, that is not yet contained in the privacyIDEA 1.1 release. So we need to copy it manually:

curl -o /opt/privacyIDEA/


In the file /etc/radddb/users we need the following entry:

DEFAULT Auth-Type := perl 

This sets the auth-type for every request to perl. Thus the request will be handled by the perl module.

Therefor the file /etc/raddb/modules/perl needs to look like this:

perl {
   module = /opt/privacyIDEA/

Remember to configure your file /etc/raddb/clients.conf according to your needs. You can at least add the localhost for testing purposes:

client {
shortname = local
secret = topsecret

Finally we need to create a file /etc/raddb/sites-available/privacyidea, that in the importand sections authorize and authenticate looks like this:

authorize {
	eap {
		ok = return
authenticate {
	Auth-Type PAP {
	Auth-Type CHAP {
	Auth-Type MS-CHAP {

(roughly we only added "perl" to the authenticate section ;-)

You can also download the file here.

We enable the site privacyidea by linking from sites-available to sites-enabled.

ln -s /etc/raddb/sites-available/privacyidea /etc/raddb/sites-enabled 

We should delete the other sites in sites-enabled.

Testing RADIUS

To test the RADIUS configuration, we stop the FreeRADIUS and start it in debug output mode:

service radiusd stop
radiusd -X

Then we use the radclient command for testing:

echo "User-Name=user, Password=pin123456" | radclient -sx localhost auth topsecret 

The output will display the total approved or denied auths.

In the FreeRADIUS ouput we can see a line like this for a successful authentication:

rlm_perl: privacyIDEA access granted 

Questions? Answers!

If you run into any problems drop me a note or you can also ask on the Google group.

Share this page:

3 Comment(s)

Add comment

Please register in our forum first to comment.


By: Alexander

Hi there!!

Any chance to have this Howto for Ubuntu, Please?



By: Matt

Enabling OTP to increase security doesn't make a lot of sense if you disable SElinux.

This tutorial is good, but it should be enhanced by leaving SElinux enabled.

By: Bob Thomson

There is some useful info on how to get SE Linux set-up on the server side of Privacy Idea here -