Skip to content

Upgrading Limesurvey with (near) zero downtime

Open Source

Limesurvey is an online survey tool. It is very powerful and commonly used in academic environments because it is Free Software (GPLv2+), allows for local installations protecting the data of participants and allowing to comply with data protection regulations. This also means there are typically no load-balanced multi-server szenarios with HA databases. But simple VMs where Limesurvey runs and needs upgrading in place.

There's an LTS branch (currently 3.x) and a stable branch (currently 4.x). There's also a 2.06 LTS branch that is restricted to paying customers. The main developers behind Limesurvey offer many services from template design to custom development to support to hosting ("Cloud", "Limesurvey Pro"). Unfortunately they also charge for easy updates called "ComfortUpdate" (currently 39€ for three months) and the manual process is made a bit cumbersome to make the "ComfortUpdate" offer more attractive.

Due to Limesurvey being an old code base and UI elements not being clearly separated, most serious use cases will end up patching files and symlinking logos around template directories. That conflicts a bit with the opaque "ComfortUpdate" process where you push a button and then magic happens. Or you have downtime and a recovery case while surveys are running.

If you do not intend to use the "ComfortUpdate" offering, you can prevent Limesurvey from connecting to daily by adding the updatable stanza as in line 14 to limesurvey/application/config/config.php:

  1. return array(
  2.  [...]
  3.          // Use the following config variable to set modified optional settings copied from config-defaults.php
  4.         'config'=>array(
  5.         // debug: Set this to 1 if you are looking for errors. If you still get no errors after enabling this
  6.         // then please check your error-logs - either in your hosting provider admin panel or in some /logs directory
  7.         // on your webspace.
  8.         // LimeSurvey developers: Set this to 2 to additionally display STRICT PHP error messages and get full access to standard templates
  9.                 'debug'=>0,
  10.                 'debugsql'=>0, // Set this to 1 to enanble sql logging, only active when debug = 2
  11.                 // Mysql database engine (INNODB|MYISAM):
  12.                  'mysqlEngine' => 'MYISAM'
  13. ,               // Update default LimeSurvey config here
  14.                 'updatable' => false,
  15.         )
  16. );

The comma on line 13 is placed like that in the current default limesurvey config.php, don't let yourself get confused. Every item in a php array must end with a comma. It can be on the next line.

The basic principle of low risk, near-zero downtime, in-place upgrades is:

  1. Create a diff between the current release and the target release
  2. Inspect the diff
  3. Make backups of the application webroot
  4. Patch a copy of the application in-place
  5. (optional) stop the web server
  6. Make a backup of the production database
  7. Move the patched application to the production webroot
  8. (if 5) Start the webserver
  9. Upgrade the database (if needed)
  10. Check the application

So, in detail:

1. Create a diff between the current release and the target release

For Limesurvey it is essential that you keep a version of the currently running release before you have applied your templates and other customizations. If you missed that step or are doing the upgrade like this the first time, the Limesurvey github releases page is a useful source but not necessarily complete (see update from 28.06.2020 below for details).

In this example we are upgrading from release 4.2.6 to 4.3.0. So we skipped 4.2.7.

I assume you're working in a /root/install directory for this step. You should definitely work outside the webroot.

You should have a 4.2.6 directory containing the unpacked Limesurvey 4.2.6 zip or tarball.

Download the 4.3.0 release, mkdir 4.3.0 and (for example) unzip -d 4.3.0/

Create the diff with # diff -N -u -r 4.2.6/ 4.3.0/ > mydiff

-N allows to create new files as a diff from /dev/zero and to remove files similarly
-u creates a "unified diff", so a diff with some context that will allow to apply patches even to files that you changed locally
-r makes the diff recurse through subdirectories

2. Inspect the diff

As a first step look at the size of the diff:

$ wc -l mydiff
49896 mydiff

That looks reasonable, it's a web app :-).

If you get 8.5m lines, you have managed to get one directory wrong and the whole app is removed and re-created.

Now read through the diff and inspect for changes that may collide with your local modifications or that look fishy.

If you see a line with "Binary files ... differ", your diff will not contain all changes. This typically happens with the binary translation files (locale/<lang>/<lang/.mo). The -a option of diff doesn't help much, so it is better to note the files and updates these with cp or rsync. You can just overwrite the whole locale/ directory hiearchy in Step #4 below if you do not translate limesurvey yourself beyond what is available from upstream. There is (currently) nothing in locale/ that is not .mo files and empty index.html files.

So grep "^Binary files .* differ$" mydiff will tell you whether the specific update of limesurvey has such binary (translation) updates, or may be contain a changed image file.

A list of the files that got created or removed may be useful to inspect at minimum:

$ diff <(cd 4.2.6; find . -type f) <(cd 4.3.0; find . -type f)
< ./limesurvey/third_party/jquery/jquery-migrate-3.1.0.js
< ./limesurvey/third_party/jquery/jquery-3.4.1.min.js
< ./limesurvey/third_party/jquery/jquery-3.4.1.js
< ./limesurvey/third_party/jquery/jquery-migrate-3.1.0.min.js
> ./limesurvey/third_party/jquery/jquery-3.5.1.min.js
> ./limesurvey/third_party/jquery/jquery-3.5.1.js
> ./limesurvey/third_party/jquery/jquery-migrate-3.3.0.min.js
> ./limesurvey/third_party/jquery/jquery-migrate-3.3.0.js
> ./limesurvey/locale/ti/index.html
> ./limesurvey/locale/ti/
> ./limesurvey/tests/data/surveys/survey_archive_265351_listParticipants.lsa
> ./limesurvey/tests/data/surveys/survey_archive_821351.lsa
> ./limesurvey/tests/unit/helpers/QuexmlPDFTest.php
> ./limesurvey/tests/unit/helpers/RemoteControlListParticipantsTest.php
< ./limesurvey/application/controllers/admin/questionedit.php
> ./limesurvey/application/controllers/QuestionEditorController.php
< ./limesurvey/application/views/admin/survey/Question2/view.php
< ./limesurvey/application/views/admin/survey/Question2/_jsVariables.php
> ./limesurvey/application/views/questionEditor/view.php
> ./limesurvey/application/views/questionEditor/_jsVariables.php
> ./limesurvey/application/views/layouts/title_bar.php
> ./limesurvey/application/views/layouts/sidemenu.php
> ./limesurvey/application/views/layouts/layout_questioneditor.php

3. Make backups of the application webroot

cd /var/www
cp -ra limesurvey limesurvey_backup_${date "+%y%m%d"} # create backup
cp -ra limesurvey limesurvey_new # create tree to patch

4. Patch a copy of the application in-place

cd limesurvey_new
patch --verbose -N -p 2 < /root/install/mydiff

The --verbose is important so you see deleted files. -N forces a forward-patch as everything else would not make sense. -p 2 strips two directories off the beginning of the paths in the diff. That makes sense as 4.3.0/limesurvey is not needed when you have limesurvey_new as your working directory.

The output will look like this:

$ patch --verbose -N -p 2 < /root/install/mydiff
Hmm...  Looks like a unified diff to me...
The text leading up to this was:
|diff -N -u -r 4.2.6/limesurvey/application/commands/DemomodeCommand.php 4.3.0/limesurvey/application/commands/DemomodeCommand.php
|--- 4.2.6/limesurvey/application/commands/DemomodeCommand.php  2020-06-02 14:10:32.000000000 +0200
|+++ 4.3.0/limesurvey/application/commands/DemomodeCommand.php  2020-06-16 08:09:50.000000000 +0200
patching file application/commands/DemomodeCommand.php
Using Plan A...
Hunk #1 succeeded at 104.
Hmm...  The next patch looks like a unified diff to me...
The text leading up to this was:
|diff -N -u -r 4.2.6/limesurvey/application/config/config-defaults.php 4.3.0/limesurvey/application/config/config-defaults.php
|--- 4.2.6/limesurvey/application/config/config-defaults.php    2020-06-02 14:10:32.000000000 +0200
|+++ 4.3.0/limesurvey/application/config/config-defaults.php    2020-06-16 08:09:50.000000000 +0200
patching file application/config/config-defaults.php
Using Plan A...
Hunk #1 succeeded at 97.
Hunk #2 succeeded at 417.
Hunk #3 succeeded at 594.


Hmm...  The next patch looks like a unified diff to me...
The text leading up to this was:
|diff -N -u -r 4.2.6/limesurvey/third_party/jquery/jquery-migrate-3.3.0.min.js 4.3.0/limesurvey/third_party/jquery/jquery-migrate-3.3.0.min.js
|--- 4.2.6/limesurvey/third_party/jquery/jquery-migrate-3.3.0.min.js    1970-01-01 01:00:00.000000000 +0100
|+++ 4.3.0/limesurvey/third_party/jquery/jquery-migrate-3.3.0.min.js    2020-06-16 08:09:50.000000000 +0200
patching file third_party/jquery/jquery-migrate-3.3.0.min.js
Using Plan A...
Hunk #1 succeeded at 1.
Removing file application/controllers/admin/questionedit.php
Removing file application/views/admin/survey/Question2/_jsVariables.php
Removing file application/views/admin/survey/Question2/view.php
Removed empty directory application/views/admin/survey/Question2
Removing file third_party/jquery/jquery-3.4.1.js
Removing file third_party/jquery/jquery-3.4.1.min.js
Removing file third_party/jquery/jquery-migrate-3.1.0.js
Removing file third_party/jquery/jquery-migrate-3.1.0.min.js

If you see any hunks rejected (.rej files created), find the reason and fix it before continuing.

You can use find /var/www/limesurvey_new -name "*.rej" to see if you have any reject files. You typically shouldn't but double checking is never bad, is it?

If you have binary files changed from Step #2 above, copy them over manually or (for the typical case of translation updates) just copy over the whole locale/ subdirectoy hierarchy. Something like rsync -a -v --delete-after /root/install/4.3.0/limesurvey/locale/ /var/www/limesurvey_new/locale/ will suffice if you are not changing translation strings locally.

5. (optional) stop the web server

If you run a stupid webserver, a complex setup, external opcode caches or just have high load (survey writes likely), you may want to stop the httpd now.

For "normal loads" (a few hundred answers to surveys per day) and when using Apache you can typically just move the webroot.

In any case even with stopping the httpd, the downtime should be very short.

6. Make a backup of the production database

Make a backup of the production database. In our case this is as simple as calling backup-mysql.

If you don't have a specific way to backup your database

mysqldump --quick --extended-insert --skip-comments limesurvey | gzip -9 > /root/install/limesurvey_database_${date "+%y%m%d"}.sql.gz

should be a good start.

7. Move the patched application to the production webroot

mv limesurvey limesurvey_old && mv limesurvey_new limesurvey

This will overwrite the old webroot and is two fast atomic operation on sane filesystems (e.g. ext4), so this is rather safe™ to do.

You can as well patch the production webroot in place. Esp. if you have stopped the httpd. You should have made a backup in step 3. You did, didn't you?

Note: If you want a fully atomic replacement of the application webroot, you have to use symlinks for the old and new tree and use mv -T, see Richard Crowley's Things UNIX can do atomically for a discussion.

8. (if 5) Start the webserver

If you stopped the webserver, start it again.

9. Upgrade the database (if needed)

Depending on whether the upgrade needed database schema upgrades, you want to inspect the Admin UI at:

The 4.2.6 -> 4.3.0 upgrade does not need any database changes.

10. Check the application

As a good last step - as always - take a critical look at the application whether everything runs as it should. Are the images loading, can you do a test survey?

If so, all fine and until the next upgrade.


25.06.2020: For the 4.3.0 to 4.3.1 update the diff -u is just 995 lines with 358 of these not being context lines only. And still most are just translation updates. The update fixes two security bugs and is essential if you run MySQL 8.0 or later. Because from that version MySQL considers "groups" a reserved name and Limesurvey uses that as a table name.

The diff is very easy to review and safe to patch in-place. The update took less than 5 minutes.

28.06.2020: The Limesurvey releases page may have releases that Github has not. E.g. the current - as of 16 June 2020 - 4.3.1 release is missing in Github still two weeks later as it has been marked in the release notes and version.php but not been tagged appropriately in the repository (yet?) cf. commit e55a12f. It is available from the Limesurvey releases page (and from the downloads page until it gets superceeded by the next 4.3.x release).

05.07.2020: 4.3.1 to 4.3.2 is just 296 lines of unified diff and seven updated .mo files. Again very little work.

13.07.2020: We skipped 4.3.3, so 4.3.2 to 4.3.4 is 84402 lines of diff (and updated translations) but we had to get the release off the github commit (essentially wget as the release 404's on the limesurvey download page and it seems the release tags on github are always only made available for the previous version when a new one is released.

This time we had a database upgrade as well and that worked fine:

Limesurvey database upgrade screenshot


No Trackbacks


Display comments as Linear | Threaded

No comments

Add Comment

Markdown format allowed
Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.

Form options

Submitted comments will be subject to moderation before being displayed.