Author: Joe Meyer
Date: July 22, 2014

Goal: To revert your Concrete5 website to a former state via a backup that you had previously taken

Difficulty: Easy

Prerequisites: Obviously in order for this to work you will need to have taken a backup of your Concrete5 website. Another thing that you will need to know is what Concrete5 version you were on when you took the backup (this can also be found by restoring your MySQL backup and looking in the config table).

WARNING: In the event you did NOT take a backup of your database prior to upgrading, do so immediately by whatever means available. At this point you may have lost data or ended up with a corrupt database and you should seek professional help. Please see our additional help section at the bottom of this tutorial.


It's not uncommon that when upgrading your Concrete5 site you get done with your upgrade, and errors appear on the page. There are many reasons this can happen, perhaps your memory limit isn't high enough, or maybe you ran out of disk space on your hosting account, or maybe there was some bug in Concrete5 that prevents you from skipping from one version to another without going through some intermediate version manually. As unfortunate as this all is, to date it has always been possible to upgrade to a later version (even if it does require a bit of finagling), and to be prepared for cases like this you should ALWAYS take a backup prior to upgrading your site.

With that said lets begin our restore.

Restore the Database

  1. If you used the built in Concrete5 backup tool prior to running your upgrade you will need to go and download a copy of the .sql file that was created. This file can be found using an ftp client or file manager that your web host provides. Here we use CPanel's file manager to go and download a copy of this file from
    . If you have multiple backups then you can look at the date on the file to help determine which one you want to restore. 

    Note: You should NOT keep these backups sitting out here as it poses a potential security risk in the event that the file permissions are ever changed.

  2. Once you have this file downloaded (note that downloading the file may require you to change the file permissions) then you will need to navigate to your favorite MySQL database tool that is capable of importing the .sql file (In this case and most others you can use PHPMyAdmin which is most likely accessible via your web hosting control panel).

    To do this, we'll open the database in PHPMyAdmin we wish to restore, then we go to the "Import" tab, attach our file and hit "go". Since the Concrete5 sql file that gets created drops the tables if they exist we don't have to worry about cleaning anything up ahead of time. 


Change the Configuration File

When you run a Concrete5 upgrade, it creates a second copy of all the core files, this way it can simply point your installation at this new copy of the files and in the case of failure you just have to point it back at the old files (this is why knowing what version you were on prior to your upgrade is important). In this example, I was trying to upgrade from to Now in order to revert my install back to I have already re-loaded my database (above) and now I need to change my configuration file to point to the appropriate directory.

  1. To determine where you need to point your install to, the first thing you need to do is check in the /updates/ folder in your site root. Does your version you were upgrading from exist there? If so then copy the directory name, you will need that in the next step. If it doesn't exist there then it means that you were previously just pointing your install at the /concrete directory (which will also be explained in the next step.

    In my instance, I have the version I was upgrading from ( in my /updates/ directory (see screenshot) and it was called concrete5.6.3.1_updater (remember the folder name as it is important for later).

  2. Now find your way over to /config/site.php. Inside of this file you will find your database connection information, and possibly some other application configurations. What we need to change (or remove) here is the line for the constant 
    <?php define('DIRNAME_APP_UPDATED', 'concrete5.6.3.1_updater');?>
    You'll notice that this is probably trying to point at the directory that you were attempting to upgrade to. Now, if you did not have your previous version in the updates folder, you are safe to simply remove this bit of code from your configuration file, that will point your install to use the /concrete directory as the core again. If you did have your previous version in the updates folder though, change the second argument of the define function to be the directory name (in my case I will change it to be "concrete5.6.2.1_updater" and in the end my config file will look something like this:

    define('DB_SERVER', 'localhost');
    define('DB_USERNAME', 'c5helpuser');
    define('DB_PASSWORD', 'redacted');
    define('DB_DATABASE', 'exchange_c5help');
    define('PASSWORD_SALT', 'redacted');
    define('DIRNAME_APP_UPDATED', 'concrete5.6.2.1_updater');                            
    Note: You should take special care to make sure there are no blank lines or whitespace at the end of your site.php file. If there is it can cause images to not display on your website or cause warnings to show up on your site.

And with that you should be reverted back to your previous version and your site should be running again.

Need Additional Help?

In the event that you need additional help with your installation there are several resources available to you.

  • First and foremost the Concrete5 community. Create an account on and post to the forums with any problems you might run across. This helps alert the developers of problems people are having so that the product can be made better or more user friendly in the future.
  • Secondly, contact your web host. Often times the updater may error due to php not being configured with enough memory or perhaps it's not running the right version. These are things that your web host should be able to help you address.
  • Thirdly, please feel free to contact ExchangeCore's development team. I list us as the last option because while we know what we're doing, we also come at a price and we hate to see you throw money away when there are other options. That said there are some pretty tough cases and if you do need our help, we can be reached by visiting our Development Contact Page.

Author: Joe Meyer
Date: June 1, 2014

Goal: To explain the differences between the old PHP MySQL extension and it's mysql_query function, and the new PDO (PHP Data Objects) functions.

Difficulty: Easy

Prerequisites: This guide is written under the assumption that you have a pretty good grasp on how programming works in general, such is if's, loops, and function calls. Having used the PHP mysql_* functions would be a big help since the purpose of this guide is to compare that to the PDO functionality.

Why do I care?

For a long time the mysql_* functions have been around with the mysql extension in PHP. They were a fairly simple way of accessing your MySQL databases, and it just worked. But now PHP has deprecated this library as of PHP 5.5, meaning that if you have deprecated warnings turned on in your PHP.ini file you will start to see deprecation messages show up on your site. This also means, that PHP is not going to support these functions in future versions and will likely remove the functionality completely. This means that you need to use an alternative option, mainly PDO_MYSQL (which we'll covere here in a bit) or you can use the newer mysqli extension.

Why is this happening?

Well, to get the full scoop on things you can hop over to PHP's website and view their wiki article on all the reasons why. Really it comes down to the following reasons:

  • Database security with the mysql extensions left a lot to be desired. The old mysql_* functions did not support prepared statements, meaning that things like MySQL injection really had to be handled by something else.
  • The old mysql extension did not support access to all the features in the newer versions of MySQL. Such as the ones listed below:
    • Stored procedures (can't handle multiple result sets)
    • Prepared Statements
    • Encryption (SSL)
    • Compression
    • Full Charset support
  • The old MySQL extension is hard to maintain code. It is not getting new features. Keeping it up to date for working with new versions of libmysql or mysqlnd versions is work, and the PHP team thinks that their time could be better spent improving other things.

Keep in mind that the mysql extension is ancient, it has been around for more than 15 years (since PHP 2.0) and so there are many concerns that such a large code base out in the wild is dependent on the older functions. But like all good things when it comes to technology, everything has an expiration date and ext/mysql's is coming up.

What about applications that use this older functionality?

Well, there is good news and bad news here. Many times when you're bolting on to an application such as wordpress or concrete5, you might be inclined to completely ignore their database abstraction layer altogether. This means that you will have the very unfortunate time of going through all your code and replacing old functions with new ones. However, if you did things properly, usually when programming inside of another application there are function calls exposed which will run your queries through their own database handler, which in turn would call the mysql or mysqli or pdo functions to get the results back. If you used these, then all you have to do is sit back and wait for the core to be updated.

Now, if you're using some ancient system that isn't supported anymore, then it may be time to think about getting it updated or to start looking for alternate solutions. In the meantime, you can continue running on older versions of PHP, and nothing is going to break, but from experience applications that go 15 years without updates become cryptic mysteries to programmers later on after the documentation has been pulled or there has been complete language overhauls done. Best to get ahead of the game on this problem.

I'm a PHP developer. What should I be doing differently?

As mentioned above the replacements for the mysql_* functions are two newer extensions, mysqli (mysql improved) and PDO_MYSQL (PHP Data Objects for MySQL). Below we'll show you some syntax differences and functionality differences between the old MySQL extension, and the new PDO extension. We normally choose to use PDO because of it's OOP style, over MySQLi, but MySQLi is a safe choice and should also be supported for many years to come.

How to Connect to MySQL using PDO

Now down to the meat and potatoes of the guide for programmers. Here I'll show you how you would have originally connected to a MySQL database using the old ext/mysql and an example of how to connect using PDO as well. Note: All code snippits below this connection one also require a connection to be present in order to run so keep this code handy.


$host = 'localhost';
$database = 'employees';
$username = 'root';
$password = 'P@ssW0rd';


//connect to database using mysql_* (the old deprecated way)
$link = mysql_connect($host, $username, $password);
if(!$link) {
    die('Error: ' . mysql_error());

$dbSelected = mysql_select_db($database, $link);
if(!$dbSelected) {
    die('Error: ' . mysql_error());

//connect to database using PDO (the new way)
try {
    $dbh = new PDO('mysql:host='.$host.';dbname='.$database, $username, $password);
} catch (PDOException $e) {
    print 'Error: ' . $e->getMessage() . '<br />';

You can see pretty quickly that PDO uses the object oriented style, and exposes functions on the object instead of setting variables and troubleshooting them individually. It also has it's own exceptions so that they are easy to detect when you build your application instead of a bunch of if blocks looking for different problems on different variable assignments.

How to execute a simple MySQL query using PDO


$query = "SELECT * FROM employees WHERE birth_date < '1955-01-01'";

//Running a simple select statement using mysql_* (the old deprecated way)
$results = mysql_query($query, $link);

while($row = mysql_fetch_assoc($results)) {
    echo $row['first_name'] . ' ' . $row['last_name'] . '<br />';

//Running a simple select statement using PDO (the new way)
//method 1:
foreach($dbh->query($query) as $row) {
    echo $row['first_name'] . ' ' . $row['last_name'] . '<br />';

//method 2:
$statement = $dbh->query($query);

while($row = $statement->fetch(PDO::FETCH_ASSOC)) {
    echo $row['first_name'] . ' ' . $row['last_name'] . '<br />';

//method 3: (This will put all results into an associative array)
$statement = $dbh->query($query);
$resultsArray = $statement->fetchAll(PDO::FETCH_ASSOC);

As you can see with PDO we can accomplish running the query and looping through the results in a much more compact foreach loop if we wish. However, if you're used to the old way with mysql of querying, then looping through in a while loop it might be easiest for you to adopt the second method shown above for PDO. There is also a great fetchAll() function with PDO that will automatically put these items into an associative array for you if you were planning on using them later. This saves you a little bit of coding effort when you have result sets that you need to use multiple times.

How to use variables in a query and protect from SQL Injection

In the olden days of MySQL you really had to go through quite an ordeal to make sure that your user input was sanitized, otherwise you could end up with a Bobby Tables fiasco. Up to this point one might argue that the differences have been pretty negligible between the old way and the new, but I think here you'll see some clear advantages to using prepared statements over the old way of having PHP sanitize your queries for you.

 * localhost/pdo.php?number=10001&salary=60117

$employeeID = $_GET['number'];
$salaryID = $_GET['salary'];

//Query with parameters for mysql_* (the old deprecated way)
$results = mysql_query(sprintf("SELECT * FROM salaries WHERE emp_no='%s' AND salary='%s'",
        mysql_real_escape_string($employeeID),mysql_real_escape_string($salaryID))) or die(mysql_error());

$rows = array();
while($row = mysql_fetch_assoc($results)){
    $rows[] = $row;

//Query with parameters for PDO (the new way)
//method 1:
$statement = $dbh->prepare('SELECT * FROM salaries WHERE emp_no=? AND salary=?');
$statement->execute(array($employeeID, $salaryID));
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);

//method 2: Bind Value
$statement = $dbh->prepare('SELECT * FROM salaries WHERE emp_no=? AND salary=?');
$statement->bindValue(1, $employeeID, PDO::PARAM_INT);
$statement->bindParam(2, $salaryID, PDO::PARAM_INT);
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);

//method 3: Named Placeholders using BindValue
$statement = $dbh->prepare('SELECT * FROM salaries WHERE emp_no=:empId AND salary=:salId');
$statement->bindValue(':empId', $employeeID, PDO::PARAM_INT);
$statement->bindValue(':salId', $salaryID, PDO::PARAM_INT);
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);

//method 4: Named Placeholders binding on execute
$statement = $dbh->prepare('SELECT * FROM salaries WHERE emp_no=:empId AND salary=:salId');
        ':empId' => $employeeID,
        ':salId' => $salaryID
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);

As you can see there are several much better methods that are far more portable in syntax than the old way of using the mysql functions. Another great advantage is that all PDO functions behave the same way, so now if you migrate over to an oracle database or perhaps microsoft sql, you don't have to worry about changing how you sanitize your queries because all of the code remains the same. You can also see how in some cases the old way could get you cross eyed pretty quick if you were binding 10 or 15 parameters in a query, and the new way (especially with the named placeholders) it is easy to tell what variable belongs where.

Need Additional Help?

If you have an old application and would like someone to take a look at it to see if this breaking change might affect you or you just have questions you think we might be able to help you answer please feel free to contact ExchangeCore's development team by visiting our Development Contact Page.

Author: Joe Meyer
Date: May 22, 2014

Goal: To upload a file to the web server, using HTML and PHP

Difficulty: Easy

Prerequisites: A good grasp on HTML forms, and a working knowledge of PHP is helpful for understanding the guide, but not necessary to get a working product (simply use the last code snippit).

There are many scripts available on the web to assist with uploading files to a server using a web browser. However, it isn't difficult to create a simple user interface that allows someone to upload files using a simple HTML form and PHP script.

In this example, I am going to use an HTML form, and it will submit to the same page. Lets start our file. We'll call it FileUpload.php.


<form action="FileUpload.php" method="post" enctype="multipart/form-data">
    <label for="upload">File:</label>
    <input type="file" name="upload" id="upload"><br/>
    <input type="submit" name="submit" value="Upload">


If we look at the first line in the body, we see our typical html form construct. The action is indicating that we'll submit this form to FileUpload.php (aka this same file), we are using a post request, and the really important part, enctype="multipart/form-data is indicating that this form may contain binary data, which is crucial to allow for the file upload to be able to take place.
Now that we have our form, we can build a little PHP logic into this file to indicate whether or not we just submitted the form (and we'll indicate that the file was uploaded), or if we are waiting on the user to submit (in which case we'll show them the form we just built). It will look something like this:

    if (isset($_FILES['upload'])) {
        //todo: handle the uploaded file
        echo "Your file was uploaded successfully";
    } else {
        <form action="FileUpload.php" method="post" enctype="multipart/form-data">
            <label for="upload">File:</label>
            <input type="file" name="upload" id="upload"><br/>
            <input type="submit" name="submit" value="Upload">


So the reason we're doing this check is because if they submitted a file, I didn't want to show them the form again. If you were submitting your file to a different page obviously you wouldn't need any of this logic, but since I kept it contained in a single file I needed to come up with the different use cases. So in order to do that I simply checked if the upload was submitted to the page or not by using the isset($_POST['upload']) check (to clarify we use "upload" because it is the name attribute on our file input). Now, we need to actually handle the file upload. Right now it's actually being placed in the web servers temporary file storage location which is defined in the php.ini file on the upload_tmp_dir setting. You can retrieve this location by using the function sys_get_temp_dir() for the generic directory, and if you want to know the file location you can use $_FILES['upload']['tmp_name'] (where upload is your form field name).

Moving on to handling the uploaded file. At this point, we're uploading a file, and it's stored in a temporary location on the server. This brings us great joy that we are able to upload files, but it's all for naught if we don't move the file to a more permanent location on disk. So lets get to it:

    if (isset($_FILES['upload'])) {
        $uploadDir = '/var/www/uploads/'; //path you wish to store you uploaded files
        $uploadedFile = $uploadDir . basename($_FILES['upload']['name']);
        if(move_uploaded_file($_FILES['upload']['tmp_name'], $uploadedFile)) {
            echo 'File was uploaded successfully.';
        } else {
            echo 'There was a problem saving the uploaded file';
        echo '<br/><a href="FileUpload.php">Back to Uploader</a>';
    } else {
        <form action="FileUpload.php" method="post" enctype="multipart/form-data">
            <label for="upload">File:</label>
            <input type="file" name="upload" id="upload"><br/>
            <input type="submit" name="submit" value="Upload">


At last, we have a working file uploader, that takes an uploaded file, and moves it over to our specified upload directory. This script should get most novices to file uploading started. There are of course many improvements that could be made on the script, and I'll list some of these considerations below but it'll be up to you to implement the code.

Additional Considerations & Tips for uploading files with PHP

  • PHP maximum upload size: php.ini settings will cause PHP to throw warings/errors when the limits are exceeded. These limit settings that should be adjusted are upload_max_filesize and post_max_size
  • Client side file size validation: This can be important to do, mainly because in the event where a file is to large you will have to first waste the users time as they upload a file which then gets rejected by the server, and to top that off they may end up with an ugly non-user friendly message on the screen.
  • Catching files that exceed the upload limits: can often be done in php by using a hidden input field with a name of MAX_FILE_SIZE which has a value that is equal to the number of bytes that the maximum upload size is. While this can be bypassed by a malicious user, those are the one's who you typically don't care if they see an ugly error message when the server finally gets around to handling it.
  • $_FILES['fieldName']['tmp_name'] is blank: - This can happen if your upload limits aren't set up to handle the size of the file you are trying to upload. Check the PHP.ini file settings again.
  • Other File Attributes: Some other $_FILE['fieldName'] attributes you might find useful include (taken from
    • $_FILES['fieldName']['name'] - The original name of the file when it was uploaded.
    • $_FILES['fieldName']['type'] - The mime type of the file, if the browser was able to provide this information. An example of this would be image/gif. This mime type is not checked server side so it may be better to implement your own mime type function to retrieve this info based on the file extension.
    • $_FILES['fieldName']['size'] - The size, in bytes, of the uploaded file.
    • $_FILES['fieldName']['tmp_name'] - The temporary path and filename where the uploaded file was stored on the server.
    • $_FILES['fieldName']['error'] - The error code associated with this file upload.
  • Paths must exists ahead of time: - Fair warning that the directory you are trying to move your upload to has to exist before you try and move the file there. This can often be done using the mkdir command.

Author: Joe Meyer
Date: May 7, 2014

Goal: Redirect to another URL using a PHP script

Difficulty: Easy

Prerequisites: A working PHP installation.

Have you ever needed to change the URL of one of your existing php pages? Perhaps you are looking for a way to redirect to another URL after someone has successfully logged in or logged out, or maybe some other kind of logic based url redirection. With PHP there are certainly ways to accomplish these things.

One way that we can achieve an immediate redirection is using the PHP header() function (Documentation here). This allows you to perform an immediate redirect. This function when used properly has the advantage of navigating to a url specified in your script, without any user interaction having to take place. Here's an example:


You may notice that I also included the exit(); below the header function, and you may be wondering if this is necessary. The short answer is no, it isn't required. However, best practices would dictate you do so and here's why. The header essentially sends a 302 redirect code back to whatever requested the page, however the script that is outputting this request will keep running until it has finished before it sends this 302. Why is this a concern? Well, perhaps your redirect was part of a conditional statement, and you wanted to redirect the user if they didn't have access to a page, but otherwise you would show a list of privileged information. Perhaps it looks something like this:

if(!$user->hasPermission()) {


Now, you might think, well that looks ok, because if they don't have permission they will get redirected, right? WRONG! The php script will continue to execute all the way through, and it will include whatever that $socialSecurityNumbers variable had in it in the response. Now, most users will never notice this, because they will be immediately redirected and never even see the information display to the screen, but this is only because that is the behavior of most browsers, not because of your programming. This is why any time you are setting the location header, you should take special care to stop your script from executing any further.

Many frameworks already have a function built in that does this for you, if you are building your own scripts without a framework, I often like to write a nice little php redirect function that looks something like this:

function redirect($url) { header('Location: '.$url); exit(); } redirect(''); //redirects to and exits the script

Please note that this is a header request, meaning that if you already have something sitting in the output buffer, this may give you a warning of "Warning: Cannot modify header information". If you decide that you want to simply discard anything in the output buffer you can use the ob_clean(); function just prior to your header() function call. However, if you decide that you really want to send the user whatever was in the output buffer, you should use ob_flush() instead of ob_clean(). Documentation on the ob_ methods can be found on

Author: Joe Meyer
Date: April 25, 2014

For people unfamiliar with Object Oriented Programming (OOP), using classes / objects can be a bit of an endeavor your first time through. However, OOP can be extremely powerful in making your code dynamic and helping to minimize your efforts when developing. It's also an excellent method for sort of lumping your code into categories that make sense.

One of the most common mistakes I see, is that people use $this when they should really be using self::. To understand when we should use each, lets create an example.


So lets say that in some PHP script, I want to be able to create some "vehicle" objects. I do this because, I want an easy way to keep track of how many tires each vehicle has, what color paint it has, and I want a function that builds me an array of cars. So my class might look something like this:

class Vehicle {
    public $numberOfTires = 0;
    public $paintColor = null;

    public function __construct($tires, $color)
        $this->numberOfTires = $tires;
        $this->paintColor = $color;

    public function paint($color)
        $this->paintColor = $color;

    public static function buildCars($amount)
        $cars = array();
        for($i = 0; $i < $amount; $i++)
            $color = self::getRandomVehicleColor();
            $cars[] = new Vehicle(4, $color);

        return $cars;

    public static function getRandomVehicleColor()
        $colors = array('black', 'blue', 'red');
        return $colors[mt_rand(0, count($colors)-1)];


So now let's break down what all of that actually does. First we have our __constructor function. This is a kind of magic PHP function that is in every class. Even if you don't define it, it is there, just empty. This is what gets called when you use new Vehicle(4, 'black');. Notice, that inside of the constructor we are setting those public variables, defined at the top of the class typically. This is essentially creating our vehicle object. So that way in our main script we can do things like this:

$myCar = new Vehicle(4, 'black');
$myBicycle = new Vehicle(2, 'red');

echo $myBicycle->numberOfTires; //echo's the number 2
echo $myCar->paintColor; //echo's the string 'black'


So you noticed that when i used $this inside of the class, I was referring to that specific instance of the object, in this case vehicle. For added re-enforcement I created the paint() function so that I can show you how you can leverage this even further. Say you created your object of $myCar, but now you want to repaint your car. That's easy enough to do by creating a function (albeit a very simple one) and then calling that function on the object you want it to apply to. Like this:

$myCar = new Vehicle(4, 'black');
echo $myCar->paintColor; //echo's the string 'black'
echo $myCar->paintColor; //echo's the string 'blue' now


So we've covered that $this is used in order to apply functions to the instance of the object. But now we want a function to create us a bunch of instances. This is something that I would still classify as being related to the object, so it makes good sense for me to create a function inside of that class to do that for me. In this case, we want to create us a bunch of cars, which are vehicles with 4 tires. We'll do this by calling a static function we built in our class.

$cars = Vehicle::buildCars(25); //return an array of 25 vehicle objects;


So, now to talk about self:: You'll notice that inside of our buildCars() function we use self:: to call the getRandomVehicleColor() function. Notice that this is also a static function, in other words, I don't actually have to have a vehicle instance, to pick a random vehicle color. These are the instances when you use self and not $this. Bottom line, $this is referencing the current instance of the object and is applied to non-static functions, self:: is for static functions that are not dependent on having an instance of the object to use or if you want to call a function from the creating class.

Author: Joe Meyer
Date: April 19, 2014

Goal: To update Concrete5 using the Concrete5 built in automated updater

Difficulty: Easy

Prerequisites: Your PHP settings must support CURL requests. This guide assumes that you are an administrator of the concrete5 website you are trying to update. While no FTP access is directly required for the upgrade, in the event your upgrade fails you will need access to directly modify files. 

Note: These screenshots were taking from an instance of concrete5 running on version 5.5. If you are using Concrete5.4 or older the administration interface may appear drastically different and may require you to go through other upgrade steps before being able to update your site. Also, please note that recovering from a failed backup is not included in this tutorial, however it does prepare you for that scenario.


One question we never hear people stop asking is "Should I upgrade my Concrete5 website to the latest version?" Our answer is almost always "It depends". We typically recommend that if it is feasible, you stay on the most current release that has been available for at least 3 weeks. Why this 3 week waiting period? Unfortunately, nobody is perfect, and this includes the developers working on the Concrete5 core. There have been countless times where the core has become corrupted or something was broken due to a human programming error, and that can cause your site to break. Waiting for a release to become a little more stable than bleeding edge is generally what we recommend to those clients of ours who don't have programming expertise to fix a bug themselves when it occurs. Typically, if a release has been available for 2-3 weeks and no new release has followed, I would consider that pretty stable. On the other hand, if you are having issues with your current version of Concrete5 that you know was fixed in a release, it may be beneficial for you to upgrade right away.

Regardless, we do think that keeping your site up to date and doing at least annual upgrades is a good idea. This helps to ensure that going forward you won't have to go through several iterations of upgrades in one shot. This is a bad thing to do because then if something does break, and you don't discover it right away, you have to try and deduce what actually caused the issue, was it the first upgrade? the second? the third? So there is a bit of a balancing act here with waiting and upgrading. If you have questions you can certainly reach out to our development staff or the Concrete5 community for expert advice or opinions.

Step One: Backing Up Your Concrete5 Website

I cannot stress enough how important it is to back up your Concrete5 website prior to doing any updates (including when you do add-on updates). While 99% of the time Concrete5 upgrades without any problems, there's always that 1% of the time where you find out that the database server crashed during the upgrade, or PHP ran out of memory halfway through, or perhaps you had a developer who thought they knew what they were doing and ended up modifying the Concrete5 core files, breaking any upgrade ability you might have had. Several of these reasons often happen on shared hosting due to lack of resources or conservative settings.

  1. The first step of backing up your Concrete5 website is to access the system section of the dashboard. This can be done by going to It can also be done by hovering over the "Dashboard" icon in the toolbar, then selecting the "System & Settings".
    C5 Dashboard Systems & Settings

  2. From the Systems & Settings page we now want to look under the "Backup & Restore" section, and select "Backup Database". You can access the backup database by using the following URL directly:
    Concrete5 Dashboard Systems & Settings - Backup Database

  3. Next click the "Run Backup" button.
    Concrete5 Systems & Settings Run Backup
    Note: As stated int he important information section, you should not keep these backups any longer than necessary. Having .sql backup files in your web root is generally a bad idea in general due to possible security implications. Also, these files can take up a lot of disk space depending on the size of your website, adding to your site backup times and possibly exhausting your disk quota with your web host.

  4. Once this completes you should now have your backup listed in the "Existing Backups" section.

Previously I stated that you do not really need to back up your file system, this is because Concrete5 doesn't overwrite any "core" files when the upgrade happens. However, it does modify your files in the /config/ folder on the root of your site. It may not be a bad idea to back these files up, since in the event you need to revert your site you would essentially just need to restore your database and the files in that directory. That said, if you don't it's usually not overly difficult to rebuild those configuration files assuming they weren't completely deleted.

Step Two: Downloading the Next Version

  1. Back on the Dashboard "Systems & Settings" page, you should select the "Update Concrete5" link. This can be accessed via
    Concrete5 Systems & Settings Update Concrete5

  2. This page may look slightly different to everyone depending on what version you are on, and what future versions are available. However, you should have a "Check for Updates" button and a "Download" button if updates are available. In this case we will select the "Download" button.
    Concrete5 Systems & Settings Download Update
    Note: Downloading is not the same as upgrading, you can download your files and come back later to do the upgrade. This is often a good idea if you don't have time to commit immediately to doing testing right after you do the upgrade, especially if your web server does not have a high amount of bandwidth available or you are testing from your home connection. If you do wait for a period of time after downloading the update, please make a new database backup just in case something changed.

    Also, on this page there are often release note highlights indicating what has changed and a link to more detailed notes. These are often a good thing to read through if you are considering moving to the most current version of Concrete5.

Step Three: Running the Updater

  1. Finally, we're to the updating part of things. After you downloaded the update, it should have redirected you to Which looks like the screenshot below. If not you can use the link posted above, or go back into the Systems & Settings page, and click on "Update Concrete5" again.
    Concrete5 Install Local Update

  2. Next, click the "Update" button showing on screen. Afterwards you should be presented with a screen saying that the update is completed. At this point, you should review your website to make sure everything is working as expected.
    Concrete5 Upgrade Complete

    Note: In the event that you received a PHP memory error or other odd error, you can re-run the update by going to

Step Four: Cleanup

Please make sure that post-upgrade you clear your cache. 

  1. This can be done by going to the "Systems & Settings" section of the Dashboard and clicking on "Clear Cache" option under the optimization.
    Concrete5 Dashboard Clear Cache

  2. Next you will be presented with a simple interface screen. Click the "Clear Cache" button and you're all set to start using your newly updated Concrete5.

  3. Once you are comfortable your install is working properly, go back to the "System & Settings" page and into the "Backup Database" page. Here you should make certain that you DELETE your backup. Alternatively, if you really want to keep this around, you should download the file, save it into some nice safe spot on your computer, and then delete it off the server.

Need Additional Help?

In the event that you need additional help with your update there are several resources available to you.

  • First and foremost the Concrete5 community. Create an account on and post to the forums with any problems you might run across. This helps alert the developers of problems people are having so that the product can be made better or more user friendly in the future.
  • Secondly, contact your web host. Often times the installer may error due to php not being configured with enough memory or perhaps it's not running the right version. These are things that your web host should be able to help you address.
  • Thirdly, please feel free to contact ExchangeCore's development team. I list us as the last option because while we know what we're doing, but we also come at a price and we hate to see you throw money away when there are other options. If you do need our help, we can be reached by visiting our Development Contact Page.

Author: Joe Meyer
Date: April 15, 2014

Goal: To install Concrete5 Manually on my cPanel web hosting account

Difficulty: Easy

Prerequisites: This guide shows you how to add files and databases using cPanel. If you are not on a hosting account where cPanel is available to you, you may have other alternatives such as FTP for file transfers and other database creation utilities. If you don't already have a web host, check out our hosting offers at our web hosting page to get started.

Concrete5 is a great user friendly content management system. Installing Concrete5 can be a bit intimidating if you don't have any experience installing websites and the hope is that by following this step by step guide, even someone who is completely unfamiliar with what a database is, or how to unzip a file, you will be able to get Concrete5 installed and off to working on your latest site. So let's get started...

Just one more quick note. Throughout this guide you see me using "c5help" in many areas. This variable simply happens to be the sub-domain I am installing to. You do not need to have any part of this when you set up your site.

Step One: Setting Up a Database

Concrete5 uses a MySQL database in order to store the dynamic content on your website. This means that you are going to need a MySQL database, a MySQL username, and a MySQL password. Here's how you will set those up if you are using a host that offers cPanel. Note: Your screen may look slightly different based on the cPanel theme you are using, the examples below were all taken from the x3 theme.

  1. Log in to your cPanel account. Usually this can be done by going to (where is your domain name).
  2. Find the "Databases" group and click on "MySQL Databases"
    cPanel MySQL Databases

  3. Next create a short and meaningful database name. Usually, I try to make my database names resemble my website names in some way. This way it is easy to identify which database belongs to which site later on if I decide to add another to my account. Do this by entering the database name and clicking "Create Database".
    cPanel Create Database

  4. Next, you will want to create a MySQL user. I strongly recommend using the password generator and here is my reasoning: 1) The password generator will ensure you have a very strong password and 2) The password generator will help ensure that you are not re-using a password you used before. This second part is important since this password will be stored in clear text in your Concrete5 configuration file. This means that any developer or anyone with FTP access to your site has access to this username and password, and it would be horrible for them to have a password that you used elsewhere. Also, you only ever have to type this password when you install Concrete5 in a later step so there's no sense in making this password something memorable. Click "Create User" once you are finished with this section.
    cPanel create MySQL user

  5. Finally, we want to assign the MySQL user we just created to our MySQL database we just created. This is a pretty straightforward process of picking them from the select lists and clicking "Add".
    cPanel add mysql user to database
    Then we click the "All Privileges" checkbox and finally the "Make Changes" button.
    cPanel MySQL Account Maintenance

Step Two: Preparing Concrete5 Files for Installation

 This next section is all about setting up the necessary files and file security for Concrete5 to work. These files will work in tandem with the database once installed to give you the powerful CMS of Concrete5.

  1. The first step to this is to download a zip of the latest version of Concrete5. You can obtain a copy of this from Then look for the heading of "Latest Stable Version" and click the download link.
    Concrete5 Download Page

  2. Once you have that downloaded go back to your cPanel home page and find the "File Manager". We will be using this to upload the zip file we just downloaded from Concrete5's website.
    cPanel File Manager

  3. At the Directory Selection prompt pick the "Document Root for:" and your website name there. Also, make sure that you have the "Show Hidden Files (dotfiles)." checkbox selected.
    cPanel File Manager Directory Selection

  4. Next, pick the "Upload" button from the upper left hand corner of the file manager.
    cPanel Upload File with File Manager

  5. A new window will open, select the "Choose File" button and pick your Concrete5 zip file you downloaded from their website. (Odds are this is probably in your downloads folder).
    cPanel File Uploader Choose File
    Once you have picked the file to be uploaded it will start uploading immediately and you will see an uploading indicator at the bottom of your browser window. Wait until this has finished to move on.
    cPanel File Uploader Status
  6. Once the status is finished (and it doesn't appear like the page is doing any more loading) you can close this browser window and go back to the file manager.
  7. Next, we need to extract the files using the file manager. Select the zip file, then click the "Extract" button in the upper right corner.
    cPanel File Manager Extract
    Choose to extract these files to the root of your website (this should be the default location if you picked the document root as instructed in the previous step). Once it's extracted it will give you some results screens and other stuff you can ignore.

  8. At this point you should now have an unzipped set of files. (You should have a folder named similar to your zip file, if so you can at this point delete the zip file).

  9. Go into the unzipped folder by either using the tree view on the left side of the file manager, or by double clicking on the folder. Inside it should appear to be the root of what a Concrete5 website looks like. (See screenshot below). Click the "Select all" checkbox at the top of the file list.
    Concrete5 File capture

  10. After you have selected all the files, we now need to move them up to the parent directory so that they are sitting at the root of your site. Do this by clicking the "Move File" option in the upper left hand corner.
    cPanel File Manager Move File

    Then remove the concrete folder from the path you want to move them to. In my case I changed the path from
    to be the root of my site:

    cPanel File Manager Move File2

  11. Now change back to your site root directory (using the tree on the left or the Up One Level icon) and delete the old concrete5.x.x.x folder (DO NOT REMOVE THE "concrete" FOLDER)
  12. Next we need to change the permissions of the config folder and the files folder to make them writable. First click on the config folder, then select "Change Permissions" on the top menu bar (make sure only config is selected)
    cPanel File Manager Change Permissions Config
    For the config folder ensure the permissions are set to 755 (Shown below)
    cPanel File Manager Chmod Config

    Next do the same thing for the "files" directory except this time use 777 for permissions. (shown below)
    cPanel File Manager Chmod Files

  13. Next we need to create a .htaccess file for the site. To do this use the "New File" button in the upper left hand corner of the File Manager. 
    cPanel File Manager New File

    The new file .htaccess file should be placed on the root of your site.
    cPanel File Manager New .htaccess File

  14. Now we need to edit the contents of this .htaccess file. Do this by selecting the file, then selecting the "Edit" button near the top of your screen. Note: If you did not check the box to show hidden files when you opened the file manager, you will not see the .htaccess file appear even though it exists.

    Once you have the editor open please populate it with the contents below and save changes:

    DirectoryIndex index.php
    RewriteEngine On
    RewriteBase /
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php/$1 [L]
    cPanel File Manager Edit .htaccess

  15. Now we are done setting up the Concrete5 files. The file manager can be closed.

 Step Three: The Install

Now we are finally ready to run the Concrete5 installer.

  1. Visit the path you installed your concrete5 site to. In this example I have put everything on the root of my subdomain so I will simply use that: but you may have put yours on the root of your domain or possibly even in a sub folder such as When you visit you should immediately be presented with a requirements page. If you have any requirements not met, you should contact your web host to find out if they are available to you.
    Concrete5 Requirements Check

  2. Click the "Continue to Installation" button.
  3. You are now on a page asking you for some information. Here's some info about each field for you:
    Site Name -  you should pick something that is less than 60 characters, since this will be used as part of the title on all of your pages and can have SEO implications.

    Administrator Information - This information is for setting up the super admin to the site. The password is something you can make up that should be easy for you to remember, since this is the account you will most likely use for making changes to the site.

    Database Information
    : Usually this will be "localhost" unless your web host is using a remote MySQL server. You will have to contact them if this is the case to get the appropriate MySQL server host name or IP address.

    MySQL Username: If you recall, way long ago back under the first section we created a MySQL user. That is what this should be. In my example this is exchange_c5help

    MySQL Password: Again, something we created back in the first section. This is the one and only time you really need to know that password you generated or created for the MySQL user. Once this step is done you can clear that notepad file or toss that post-it note.

    Database Name: Again, set up back in the first section. It just so happens that I named mine the same as my MySQL user, exchange_c5help. 

    Sample Content
    This part is entirely up to you. If you know what you're doing a blank site should suit you well. Otherwise, per C5's recommendation, I'll also recommend you start with the sample site.

    Here's a screenshot of what my final install page looks like:
    Concrete5 Install Page

  4. Click the "Install concrete5" button and wait for the installer to finish. Once it has (hopefully without error) you should be logged in as the admin and you can now start building your website!

Need Additional Help?

In the event that you need additional help with your installation there are several resources available to you.

  • First and foremost the Concrete5 community. Create an account on and post to the forums with any problems you might run across. This helps alert the developers of problems people are having so that the product can be made better or more user friendly in the future.
  • Secondly, contact your web host. Often times the installer may error due to php not being configured with enough memory or perhaps it's not running the right version. These are things that your web host should be able to help you address.
  • Thirdly, please feel free to contact ExchangeCore's development team. I list us as the last option because while we know what we're doing, we also come at a price and we hate to see you throw money away when there are other options. If you do need our help, we can be reached by visiting our Development Contact Page.