How To Install WordPress with a Managed Database on Ubuntu 18.04

A previous version of this tutorial was written by Justin Ellingwood


WordPress is the most popular CMS (content management system) on the internet. It’s a great choice for getting a website up and running quickly, and after the initial setup, almost all administration can be done through the web frontend.

WordPress is designed to pull content – including posts, comments, user profiles, and other data – from a database backend. As a website grows and must satisfy more and more traffic, it can eventually outgrow its initial database. To resolve this, one can scale up their database by migrating their data to a machine with more RAM or CPU, but this is a tedious process that runs the risk of data loss or corruption. This is why some WordPress developers choose to build their websites on managed databases, which allow users to scale their database automatically with a far lower risk of data loss.

In this guide, we’ll focus on setting up a WordPress instance with a managed MySQL database and an Ubuntu 18.04 server. This will require you to install PHP and Apache to serve the content over the web.


In order to complete this tutorial, you will need:

  • Access to an Ubuntu 18.04 server: This server should have a non-root sudo-enabled user and a firewall configured. You can set this up by following our Ubuntu 18.04 initial server setup guide.
  • A managed MySQL database: To provision a Managed MySQL Database from DigitalOcean, see our Managed Databases product documentation. Note that this guide will refer to DigitalOcean Managed Databases in examples, but the instructions provided here should also generally work for managed MySQL databases from other cloud providers.
  • A LAMP stack installed on your server: In addition to a database, WordPress requires a web server and PHP to function correctly. Setting up a complete LAMP stack (Linux, Apache, MySQL, and PHP) fulfills all of these requirements. Follow this guide to install and configure this software. As you follow this guide, make sure that you set up a virtual host to point to a domain name that you own. Additionally, be sure to skip Step 2, as installing mysql-server on your machine will make your managed database instance redundant.
  • TLS/SSL security implemented for your site: If you have a domain name, the easiest way to secure your site is with Let’s Encrypt, which provides free, trusted certificates. Follow our Let’s Encrypt guide for Apache to set this up. Note that this will also require you to obtain a domain name and set up DNS records on your server. Follow this introduction to DigitalOcean DNS for details on how to configure this. Altneratively, if you don’t have a domain name, you use a self-signed certificate for your site.

When you are finished with the setup steps, log into your server as your non-root user and continue below.

Step 1 – Adding the MySQL Software Repository and Installing mysql-client

In order to configure your managed MySQL instance, you will need to install a client that will allow you to access the database from your server. This step will walk you through the process of installing the mysql-client package.

In many cases, you can just install mysql-client with the apt command, but if you’re using the default Ubuntu repositories this will install version 5.7 of the program. In order to access a DigitalOcean Managed MySQL database, you will need to install version 8.0 or above. To do so, you must first add the MySQL software repository before installing the package.

Begin by navigating to the MySQL APT Repository page in your web browser. Find the Download button in the lower-right corner and click through to the next page. This page will prompt you to log in or sign up for an Oracle web account. You can skip that and instead look for the link that says No thanks, just start my download. Right-click the link and select Copy Link Address (this option may be worded differently, depending on your browser).

Now you’re ready to download the file. On your server, move to a directory you can write to:

  • cd /tmp

Download the file using curl, remembering to paste the address you just copied in place of the highlighted portion of the following command. You also need to pass two command line flags to curl. -O instructs curl to output to a file instead of standard output. The L flag makes curl follow HTTP redirects, which is necessary in this case because the address you copied actually redirects to another location before the file downloads:

  • curl -OL

The file should now be downloaded in your current directory. List the files to make sure:

  • ls

You will see the filename listed in the output:

mysql-apt-config_0.8.13-1_all.deb . . .

Now you can add the MySQL APT repository to your system’s repository list. The dpkg command is used to install, remove, and inspect .deb software packages. The following command includes the -i flag, indicating that you’d like to install from the specified file:

  • sudo dpkg -i mysql-apt-config*

During the installation, you’ll be presented with a configuration screen where you can specify which version of MySQL you’d prefer, along with an option to install repositories for other MySQL-related tools. The defaults will add the repository information for the latest stable version of MySQL and nothing else. This is what we want, so use the down arrow to navigate to the Ok menu option and hit ENTER.

Selecting mysql-apt-config configuration options

Following that, the package will finish adding the repository. Refresh your apt package cache to make the new software packages available:

  • sudo apt update

Next, you can clean up your system a bit and delete the file you downloaded, as you won’t need it in the future:

  • rm mysql-apt-config*

Note: If you ever need to update the configuration of these repositories, just run the following command to select your new options:

  • sudo dpkg-reconfigure mysql-apt-config

After selecting your new options, run the following command to refresh your package cache:

  • sudo apt update

Now that you’ve added the MySQL repositories, you’re ready to install the actual MySQL client software. Do so with the following apt command:

  • sudo apt install mysql-client

Once that command finishes, check the software version number to ensure that you have the latest release:

  • mysql --version
mysql Ver 8.0.17-cluster for Linux on x86_64 (MySQL Community Server - GPL)

You’re now able to connect to your managed database and begin preparing it to function with WordPress.

Step 2 – Creating a MySQL Database and User for WordPress

WordPress uses MySQL to manage and store site and user information. Assuming you have completed all the prerequisite tutorials, you will have already provisioned a managed MySQL instance. Here, we’ll take the preparatory step of creating a database and a user for WordPress to use.

Most managed database providers provide a uniform resource identifier (URI) used for connecting to the database instance. If you’re using a DigitalOcean Managed Database, you can find the relevant connection information in your Cloud Control Panel.

First, click Databases in the left-hand sidebar menu and select the MySQL database you want to use for your WordPress installation. Scroll down to the Connection Details section and copy the link in the host field. Then paste this link into the following command, replacing host_uri with the information you just copied. Likewise, copy the port number in the port field – which will be 25060 on a DigitalOcean Managed Database – and replace port with that number. Additionally, if this is your first time connecting to your managed database and you’ve not created your own administrative MySQL user, copy the value in the username field and paste it into the command, replacing user:

  • mysql -u user -p -h host_uri -P port

This command includes the -p flag, which will prompt you for the password of the MySQL user you specified. For a DigitalOcean Managed Database’s default doadmin user, you can find this by clicking the show link in the Connection Details section to reveal the password. Copy and paste it into your terminal when prompted.

Note: If you are not using a DigitalOcean Managed Database, your connection options may differ. If that’s the case, you should consult your provider’s documentation for instructions on connecting third party applications to your database.

From the MySQL prompt, create a new database that WordPress will control. You can call this whatever you would like, but we will use the name wordpress in this guide to keep it simple. Create the database for WordPress by typing:

  • CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;

Note: Every MySQL statement must end in a semi-colon (;). Check to make sure this is present if you are running into any issues.

Next, create a new MySQL user account that you will use exclusively to operate on the new database. Creating single-purpose databases and accounts is a good idea from a management and security standpoint. We will use the name wordpressuser in this guide, but feel free to change this if you’d like.

Run the following command, but replace your_server_ip with your Ubuntu server’s IP address. Be aware, though, that this will limit wordpressuser to only be able to connect from your LAMP server; if you plan to manage WordPress from your local computer, you should enter that machine’s IP address instead. Additionally, choose a strong password for your database user:

  • CREATE USER 'wordpressuser'@your_server_ip IDENTIFIED WITH mysql_native_password BY 'password';

Note: If you do not know what your server’s public IP address is, there are a number of ways you can find it. Usually, this is the address you use to connect to your server through SSH.

One method is to use the curl utility to contact an outside party to tell you how it sees your server. For example, you can use curl to contact an IP-checking tool like ICanHazIP:

  • curl

This command will return your server’s public IP address in your output.

Then grant this user access to the database you just created. Do so by running the following command:

  • GRANT ALL ON wordpress.* TO 'wordpressuser'@your_server_ip;

You now have a database and user account, each made specifically for WordPress. Next, you need to flush the privileges so that the current MySQL session recognizes the changes you’ve made:


Following that, exit out of MySQL by typing:

  • exit

That takes care of configuring your managed MySQL database to function with WordPress. In the next step, you will install a few PHP extensions in order to get more functionality out of the CMS.

Step 3 – Installing Additional PHP Extensions

Assuming you followed the prerequisite LAMP stack tutorial, you will have installed a few extensions intended to get PHP to properly communicate with MySQL. WordPress and many of its plugins leverage additional PHP extensions to add additional functionalities.

To download and install some of the more popular PHP extensions for use with WordPress, run the following command:

  • sudo apt install php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl php-zip

Note: Each WordPress plugin has its own set of requirements. Some may require you to install additional PHP packages. Check your plugin documentation to see which extensions it requires. If they are available, they can be installed with apt as demonstrated above.

You will restart Apache to load these new extensions in the next section. If you’re returning here to install additional plugins, though, you can restart Apache now by typing:

  • sudo systemctl restart apache2

Otherwise, continue on to Step 4.

Step 4 – Adjusting Apache’s Configuration to Allow for .htaccess Overrides and Rewrites

In order for Apache to be able to properly serve your WordPress installation, you must make a few minor adjustments to your Apache configuration.

If you followed the prerequisite tutorials, you should already have a configuration file for your site in the /etc/apache2/sites-available/ directory. We’ll use /etc/apache2/sites-available/your_domain.conf as an example here, but you should substitute the path to your configuration file where appropriate.

Additionally, we will use /var/www/your_domain as the root directory in this example WordPress install. You should use the web root specified in your own configuration.

Note: It’s possible you are using the 000-default.conf default configuration (with /var/www/html as your web root). This is fine to use if you’re only going to host one website on this server. If not, it’s best to split the necessary configuration into logical chunks, one file per site.

Currently, the use of .htaccess files is disabled. WordPress and many WordPress plugins use these files extensively for in-directory tweaks to the web server’s behavior.

Open the Apache configuration file for your website:

  • sudo nano /etc/apache2/sites-available/your_domain.conf

To allow .htaccess files, you need to set the AllowOverride directive within a Directory block pointing to your document root. Add the following block of text inside the VirtualHost block in your configuration file, being sure to use the correct web root directory:

<Directory /var/www/your_domain>     AllowOverride All </Directory> 

When you are finished, save and close the file.

Next, enable mod_rewrite so that you can employ the WordPress permalink feature:

  • sudo a2enmod rewrite

Before implementing the changes you’ve just made, check to make sure there aren’t any syntax errors in your configuration file:

  • sudo apache2ctl configtest

The output might have a message that looks like this:

AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using Set the 'ServerName' directive globally to suppress this message Syntax OK

If you wish to suppress the top line, just add a ServerName directive to your main (global) Apache configuration file at /etc/apache2/apache2.conf. The ServerName can be your server’s domain or IP address. However, this is just a message; it doesn’t affect the functionality of your site and as long as the output contains Syntax OK, you’re all set to continue.

Restart Apache to implement the changes:

  • sudo systemctl restart apache2

With that, you’re ready to download and set up WordPress itself.

Step 5 – Downloading WordPress

Now that your server software is configured, you can install and configure WordPress. For security reasons, it is always recommended to get the latest version of WordPress from their site.

First, navigate to into a writable directory. /tmp will work for the purposes of this step:

  • cd /tmp

Then download the compressed release by typing:

  • curl -O

Extract the compressed file to create the WordPress directory structure:

  • tar xzvf latest.tar.gz

You will move these files into your document root momentarily. Before doing so, add a dummy .htaccess file so that this will be available for WordPress to use later.

Create the file by typing:

  • touch /tmp/wordpress/.htaccess

Also, copy over the sample configuration file to the filename that WordPress actually reads:

  • cp /tmp/wordpress/wp-config-sample.php /tmp/wordpress/wp-config.php

Create an upgrade directory, so that WordPress won’t run into permissions issues when trying to do this on its own following an update to its software:

  • mkdir /tmp/wordpress/wp-content/upgrade

Then copy the entire contents of the directory into your document root. The following command uses a period at the end of the source directory to indicate that everything within the directory should be copied, including hidden files (like the .htaccess file you just created):

  • sudo cp -a /tmp/wordpress/. /var/www/your_domain

That takes care of downloading WordPress onto your server. At this point, though, you still won’t be able to access the WordPress setup interface in your browser. To fix that, you’ll need to make a few changes to your server’s WordPress configuration.

Step 6 – Configuring the WordPress Directory

Before going through the web-based WordPress setup, you need to adjust some items in your WordPress directory. One important configuration change involves setting up reasonable file permissions and ownership.

Start by giving ownership of all the files to the www-data user and group. This is the user that the Apache web server runs as on Debian and Ubuntu systems, and Apache will need to be able to read and write WordPress files in order to serve the website and perform automatic updates.

Update the ownership of your web root directory with chown:

  • sudo chown -R www-data:www-data /var/www/your_domain

Next run the following two find commands to set the correct permissions on the WordPress directories and files:

  • sudo find /var/www/your_domain/ -type d -exec chmod 750 {} \;
  • sudo find /var/www/your_domain/ -type f -exec chmod 640 {} \;

These should be a reasonable permissions set to start with. Be aware, though, that some plugins and procedures might require additional updates.

Now, you need to make some changes to the main WordPress configuration file.

When you open the file, the first order of business will be to replace some secret keys to provide security for your installation. WordPress provides a secure generator for these values so that you do not have to try to come up with good values on your own. These are only used internally, so it won’t hurt usability to have complex, secure values here.

To grab secure values from the WordPress secret key generator, run the following command:

  • curl -s

You will get back unique values that look something like this:

Warning! It is important that you request unique values each time. Do NOT copy the values shown here!

define('AUTH_KEY', '1jl/vqfs<XhdXoAPz9 DO NOT COPY THESE VALUES c_j{iwqD^<+c9.k<J@4H'); define('SECURE_AUTH_KEY', 'E2N-h2]Dcvp+aS/p7X DO NOT COPY THESE VALUES {Ka(f;rv?Pxf})CgLi-3'); define('LOGGED_IN_KEY', 'W(50,{W^,OPB%PB<JF DO NOT COPY THESE VALUES 2;y&,2m%3]R6DUth[;88'); define('NONCE_KEY', 'll,4UC)7ua+8<!4VM+ DO NOT COPY THESE VALUES #`DXF+[$ atzM7 o^-C7g'); define('AUTH_SALT', 'koMrurzOA+|L_lG}kf DO NOT COPY THESE VALUES 07VC*Lj*lD&?3w!BT#-'); define('SECURE_AUTH_SALT', 'p32*p,]z%LZ+pAu:VY DO NOT COPY THESE VALUES C-?y+K0DK_+F|0h{!_xY'); define('LOGGED_IN_SALT', 'i^/G2W7!-1H2OQ+t$ 3 DO NOT COPY THESE VALUES t6**bRVFSD[Hi])-qS`|'); define('NONCE_SALT', 'Q6]U:K?j4L%Z]}h^q7 DO NOT COPY THESE VALUES 1% ^qUswWgn+6&xqHN&%');

These are configuration lines that you can paste directly into your configuration file to set secure keys. Copy the output you received now.

Then, open the WordPress configuration file:

  • sudo nano /var/www/your_domain/wp-config.php

Find the section that contains the dummy values for those settings. It will look something like this:

. . .  define('AUTH_KEY',         'put your unique phrase here'); define('SECURE_AUTH_KEY',  'put your unique phrase here'); define('LOGGED_IN_KEY',    'put your unique phrase here'); define('NONCE_KEY',        'put your unique phrase here'); define('AUTH_SALT',        'put your unique phrase here'); define('SECURE_AUTH_SALT', 'put your unique phrase here'); define('LOGGED_IN_SALT',   'put your unique phrase here'); define('NONCE_SALT',       'put your unique phrase here');  . . . 

Delete those lines and paste in the values you copied from the command line:


Next you need to modify some of the database connection settings at the beginning of the file. First, update the 'DB_NAME', 'DB_USER', and 'DB_PASSWORD' fields to point to the database name, database user, and the associated password that you configured within MySQL:

. . . /** The name of the database for WordPress */ define('DB_NAME', 'wordpress');  /** MySQL database username */ define('DB_USER', 'wordpressuser');  /** MySQL database password */ define('DB_PASSWORD', 'password');  . . . 

You will also need to replace localhost in the 'DB_HOST' field with your managed database’s host. Additionally, append a colon (:) and your database’s port number to the host:

. . .  /** MySQL hostname */ define( 'DB_HOST', 'managed_database_host:managed_database_port' );  . . . 

The last change you need to make is to set the method that WordPress will use to write to the filesystem. Since you’ve already given the web server permission to write where it needs to, you can explicitly set the filesystem method to direct port. Failure to set this with your current settings would result in WordPress prompting for FTP credentials when you perform certain actions.

This setting can be added below the database connection settings, or anywhere else in the file:

. . .  define('FS_METHOD', 'direct'); . . . 

Save and close the file when you are finished.

After making those changes, you’re all set to finish the process of installing WordPress in your web browser. However, there’s one more step that we recommend you complete to add an extra layer of security to your configuration.

At this point, your WordPress installation is communicating with your managed MySQL database. However, there’s no guarantee that data transfers between the two machines are secure. In this step, we will configure WordPress to communicate with your MySQL instance over a TLS/SSL connection to ensure secure communications between the two machines.

To do so, you’ll need your managed database’s CA certificate. For a DigitalOcean Managed Database, you can find this by once again navigating to the Databases tab in your Control Panel. Click on your database, and find the Connection Details section. There will be a button there that reads Download the CA certificate. Click this button to download the certificate to your local machine.

Then transfer this file to your WordPress server. If your local machine is running Linux or macOS, you can use a tool like scp:

  • scp /path/to/file/ca-certificate.crt sammy@your_server_ip:/tmp

If your local machine is running Windows, you can use an alternative tool like WinSCP.

Once the CA certificate is on your server, move it to the /user/local/share/ca-certificates/ directory, Ubuntu’s trusted certificate store:

  • sudo mv /tmp/ca-certificate.crt /usr/local/share/ca-certificates/

Following this, run the update-ca-certificates command. This program looks for certificates within /usr/local/share/ca-certificates, adds any new ones to the /etc/ssl/certs/ directory, and generates a list of trusted SSL certificates based on its contents:

  • sudo update-ca-certificates

Then, reopen your wp-config.php file:

  • nano /var/www/your_domain/wp-config.php

Somewhere in the file, add the following line:


Save and close the file.

Following that, WordPress will securely communicate with your managed MySQL database.

Step 8 – Completing the Installation Through the Web Interface

Now that the server configuration is complete, you can complete the installation through the WordPress web interface.

In your web browser, navigate to your server’s domain name or public IP address:


Assuming there aren’t any errors in your WordPress or Apache configurations, you’ll see the WordPress language selection splash page. Select the language you would like to use:

WordPress language selection

After selecting your language, you will see the main setup page.

Select a name for your WordPress site and choose a username (it is recommended not to choose something like “admin” for security purposes). A strong password is generated automatically. Save this password or enter an alternative strong password.

Enter your email address and select whether you want to discourage search engines from indexing your site:

WordPress setup installation

When you click ahead, you will be taken to a page that prompts you to log in:

WordPress login prompt

Once you log in, you will be taken to the WordPress administration dashboard:

WordPress login prompt

From here, you can begin customizing your new WordPress site and start publishing content. If this is your first time using WordPress, we encourage you to explore the interface a bit to get acquainted with your new CMS.


By completing this guide, you will have WordPress installed and ready to use on your server. Additionally, your WordPress installation is dynamically pulling posts, pages, and other content from your managed MySQL database.

Some common next steps are to choose the permalinks setting for your posts. This setting can be found under Settings > Permalinks. You could also select a new theme in Appearance > Themes. Once you start loading some content into your site, you could also configure a CDN to speed up your site’s asset delivery.

DigitalOcean Community Tutorials

Learn PyQt: Embedding PyQtGraph (or any other custom PyQt5 widgets) from Qt Designer

Qt Designer is a great tool for designing PyQt5 GUIs, allowing you to use the entire range of Qt5 widgets and layouts to construct your apps. As your applications get more complex however you may find yourself creating custom widgets, or using PyQt5 libraries such as PyQtGraph, who’s widgets are not available within Designer.

Helpfully, Qt Designer supports a mechanism for using placeholder widgets to represent your custom or external widgets in your design. This tutorial will walk you through the process of using placeholders to include a PyQtGraph plot in your app from within Qt Designer.

Promoting Widgets

The principle of using placeholders in Qt Designer is quite straightforward — 

  1. Create a UI as normal in Qt Designer.
  2. Add a placeholder widget to represent the custom widget you’re adding.
  3. Tell Qt to replace your placeholder with your actual widget when building the UI.

In Qt this final step is referred to as promoting (as in promoting a base class).

If the custom widget you are adding is a subclass of an existing Qt widget, you may want to use the base class as your placeholder to promote from. For example, if you have a custom MyAwesomeButton button widget subclassed from QPushButton use QPushButton as the placeholder and promote it to MyAwesomeButton. This gives you access to the base class properties, events and actions from within Qt Designer.

If you don’t have an obvious base class to use, then you can use QWidget, the common base class of all Qt widgets.


Data science is one of the post popular uses of Python, and building dashboards and analysis tools is a common use case of PyQt5. For all of these being able to add plots to your UI is very useful — and being able to do this from Qt Designer even more so.

There are a number of plotting libraries available in Python, with matplotlib being the most popular and offering some basic support for PyQt5. PyQtGraph is an popular alternative which uses Qt’s native QGraphicsScene to provide fast zooming, scaling, drag-drop behaviour that feels a natural part of your application.

Whether you’re using PyQtGraph or maplotlib for your plotting needs, the plot canvas widgets are not available from within Qt Designer. In this tutorial I’ll walk you through the process of using these custom widgets in your apps.

If you don’t have PyQtGraph installed already, you can install it using:

pip install pyqtgraph 

The instructions below aren't specific to PyQtGraph, and you can use the same process to add matplotlib or any other custom widgets to your app.

Qt Designer

We will be using Qt Designer to create a simple UI design, and adding a placeholder for our PyQtGraph widget. First open Qt Designer and create a new QMainWindow as normal.

Qt Creator — Select MainWindow for widget type Qt Creator — Select MainWindow for widget type

We next need to add the placeholder widget. As there is no suitable baseclass for the PyQtGraph plot widget, we’ll use the basic QWidget as our placeholder. Select the Widget from the left sidebar and place it in the centre of your window.

Give the widget a name, “graphWidget” will do. This is just a tag to reference the element in code.

Add a widget to the window. Name the widget as Add a widget to the window. Name the widget as "graphWidget"

Right click on the widget and select Promote to from the widget’s context menu.

Promoting a QWidget indicates that it should be replaced with the specified subclass, in our case the PyQtGraph plot widget.

A promoted widget can be reverted back to its base class by right-clicking and choosing Demote to from the widget's context menu.

Right click to show the promotion menu Right click to show the promotion menu

You will be presented with a dialog to specify the custom widget class the placeholder widget will become.

The header file is the name of the Python module used to import the class, which is pyqtgraph. Specify PlotWidget as the class name of the widget to replace it with.

Promote the widget by specifying the class name as PlotWidget and header file as pyqtgraph. Promote the widget by specifying the class name as PlotWidget and header file as pyqtgraph.

The name you use for the file doesn't matter, but it's usually a good idea to name it after the class you're going to create with it.

Voila! The widget is now promoted to a canvas to plot. But you won’t be able to see any changes within Qt Designer. Save the window as mainwindow.ui in the same directory as your PyQt app.

For a complete guide to using Qt Designer .ui files from Python check out First steps with Qt Creator.

Loading the .ui file

We now have the mainwindow.ui file containing our UI definition. We can load this from Python to show the window and our custom widget.

Let’s start from a basic app template.


from PyQt5 import QtWidgets, uic import pyqtgraph as pg import sys  class MainWindow(QtWidgets.QMainWindow):      def __init__(self, *args, **kwargs):         super(MainWindow, self).__init__(*args, **kwargs)          #Load the UI Page         uic.loadUi('mainwindow.ui', self)   def main():     app = QtWidgets.QApplication(sys.argv)     main = MainWindow()     sys.exit(app.exec_())  if __name__ == '__main__':              main() 

Save the code above in the same folder as your mainwindow.ui file, and run it as normal.

Your graph is now embedded Your graph is now embedded

You should see a window with your widget transformed into a PyQtGraph plotting widget.

Let’s now create a function to make a simple plot of x and y data.


from PyQt5 import QtWidgets, uic from pyqtgraph import PlotWidget, plot import pyqtgraph as pg import sys  # We need sys so that we can pass argv to QApplication import os  class MainWindow(QtWidgets.QMainWindow):      def __init__(self, *args, **kwargs):         super(MainWindow, self).__init__(*args, **kwargs)          #Load the UI Page         uic.loadUi('mainwindow.ui', self)          self.plot([1,2,3,4,5,6,7,8,9,10], [30,32,34,32,33,31,29,32,35,45])      def plot(self, hour, temperature):         self.graphWidget.plot(hour, temperature)  def main():     app = QtWidgets.QApplication(sys.argv)     main = MainWindow()     sys.exit(app.exec_())  if __name__ == '__main__':           main() 

So we added the plot() method which accepts two arrays, temp Temperature and hour Hour, then plots the data using the graph widget .plot() method.

Run the code, you should see the following.

The custom PyQtGraph widget showing dummy data. The custom PyQtGraph widget showing dummy data.

That’s it! You have just embedded your first plot with PyQtGraph.

The default PyQtGraph plot isn’t very pretty, however can play around with the .plot() call to change the data shown.

We’ll cover more complex PyQtGraph plots and plot customization, including line colours, styles and alternative types of plots in an upcoming tutorial.

Planet Python

How To Install Webmin on Debian 10


Webmin is a modern web control panel that allows you to administer your Linux server through a browser-based interface. With Webmin, you can manage user accounts, configure DNS settings, and change settings for common packages on the fly.

In this tutorial, you’ll install and configure Webmin on your server and secure access to the interface with a valid certificate from Let’s Encrypt. You’ll then use Webmin to add new user accounts, and update all packages on your server from the dashboard.


To complete this tutorial, you will need:

Step 1 — Installing Webmin

First, we need to add the Webmin repository so that we can install and update Webmin using our package manager. We do this by adding the repository to the /etc/apt/sources.list file.

Open the file in your preferred editor. Here, we’ll use nano:

  • sudo nano /etc/apt/sources.list

Then add this line to the bottom of the file to add the new repository:

 . . .  deb sarge contrib 

Save the file and exit the editor. If you used nano, do so by pressing CTRL+X, Y, then ENTER.

Next, you’ll add the Webmin PGP key so that your system will trust the new repository. In order to do that, though, you must install the gnupg1 package, which is GNU’s tool for secure communication and data storage.

Update your server’s package index if you’ve not done so recently:

  • sudo apt update

Then install gnupg1:

  • sudo apt install gnupg1

Following that, download the Webmin PGP key with wget:

  • wget

Then add the package key:

  • sudo apt-key add jcameron-key.asc

Next, update the list of packages again in order to include the now-trusted Webmin repository:

  • sudo apt update

Then install Webmin:

  • sudo apt install webmin

Once the installation finishes, you’ll be presented with the following output:

. . . Webmin install complete. You can now login to https://your_server:10000 as root with your root password, or as any user who can use sudo.

Note: If you installed and enabled ufw during the prerequisite step, you will need to run the following command in order to allow Webmin through the firewall:

  • sudo ufw allow 10000

For extra security, you may want to configure your firewall to only allow access to this port from certain IP ranges.

Let’s secure access to Webmin by adding a valid certificate.

Step 2 — Adding a Valid Certificate with Let’s Encrypt

Webmin is already configured to use HTTPS, but it uses a self-signed, untrusted certificate. Let’s replace it with a valid certificate from Let’s Encrypt.

Navigate to https://your_domain:10000 in your web browser, replacing your_domain with the domain name pointing to your server’s IP address.

Note: When logging in for the first time, you will see an “Invalid SSL” warning. This warning may say something different depending on your browser, but the reason for it is that the server has generated a self-signed certificate. Allow the exception and proceed to your domain so you can replace the self-signed certificate with one from Let’s Encrypt.

You’ll be presented with a login screen. Sign in with the non-root user you created while fulfilling the prerequisites for this tutorial.

Once you log in, the first screen you will see is the Webmin dashboard. Before you can apply a valid certificate, you have to set the server’s hostname. Look for the System hostname field and click on the link to the right, as shown in the following figure:

Image showing where the link is on the Webmin dashboard

This will take you to the Hostname and DNS Client page. Locate the Hostname field, and enter your Fully-Qualified Domain Name into the field. Then click the Save button at the bottom of the page to apply the setting.

After you’ve set your hostname, click on the Webmin dropdown menu in the left-hand navigation bar, and then click on Webmin Configuration.

From the Webmin Configuration page, select SSL Encryption from the list of icons, and then click on the Let’s Encrypt tab. You’ll see a screen like the following figure:

Image showing the Let's Encrypt tab of the SSL Encryption section

On this page, you’ll tell Webmin how to obtain and renew your certificate. Let’s Encrypt certificates expire after 3 months, but you can instruct Webmin to automatically attempt to renew the Let’s Encrypt certificate every month. Let’s Encrypt looks for a verification file on the server, so we’ll configure Webmin to place the verification file inside the folder /var/www/your_domain, which is the folder that the Apache web server you configured in the prerequisites uses. Follow these steps to set up your certificate:

  1. Fill in Hostnames for certificate with your FQDN.
  2. For Website root directory for validation file, select the Other Directory button and enter your website’s document root. Assuming you followed the prerequisite Apache tutorial this will be /var/www/your_domain.
  3. For Months between automatic renewal section, deselect the Only renew manually option by typing 1 into the input box, and select the radio button to the left of the input box.

Click the Request Certificate button. After a few seconds, you will see a confirmation screen.

To use the new certificate, click the Return to Webmin configuration button on the confirmation screen. From that page, scroll down and click the Restart Webmin button. Wait around 30 seconds, and then reload the page and log in again. Your browser should now indicate that the certificate is valid.

Step 3 – Using Webmin

You’ve now set up a secured working instance of Webmin. Let’s look at how to use it.

Webmin has many different modules that can control everything from the BIND DNS Server to something as simple as adding users to the system. Let’s look at how to create a new user, and then explore how to update your system’s packages using Webmin.

Managing Users and Groups

Let’s explore how to manage the users and groups on your server.

First, click the System dropdown menu in the left-hand sidebar, and then click the link for Users and Groups. From here, you can add and manage users and groups.

Let’s create a new user called deploy which you can use to host web applications. When creating a user, you can set options for password expiry, the user’s shell, and whether or not they are allowed a home directory.

To add a user, click Create a new user, which is located at the top of the users table. This displays the Create User screen, where you can supply the username, password, groups and other options. Follow these instructions to create the user:

  1. Fill in Username with deploy.
  2. Select Automatic for User ID.
  3. Fill in Real Name with a descriptive name like Deployment user.
  4. For Home Directory, select Automatic.
  5. For Shell, select /bin/bash from the dropdown list.
  6. For Password, select Normal Password and type in a password of your choice.
  7. Jump down to Primary Group and select New group with same name as user.
  8. For Secondary Group, select sudo from the All groups list. This should automatically be added to the In groups list, but if it isn’t press the -> button to add it.

After making those selections, press Create. This will create the deploy user in short order.

Next, let’s look at how to install updates to our system.

Updating Packages

Webmin lets you update all of your packages through its user interface. To update all of your packages, first, click the Dashboard button above the left-hand sidebar, and then locate the Package updates field. If there are updates available, you’ll see a link that states the number of available updates.

Click this link, and then press Update selected packages to start the update. You may be asked to reboot the server, which you can also do through the Webmin interface.


You now have a secured working instance of Webmin and you’ve used the interface to create a user and update packages. Webmin gives you access to many things you’d normally need to access through the console, and it organizes them in an intuitive way. For example, if you have Apache installed, you would find the configuration tab for it under Servers, and then Apache.

Explore the interface, or read the Official Webmin wiki to learn more about managing your system with Webmin.

DigitalOcean Community Tutorials

What’s Your Sign? Team Building with Personality Frameworks

I have ALWAYS loved personality tests. Before Buzzfeed quizzes like “What Season Suits Your Personality?” were common—even before the Myers Briggs or Enneagram trended—I would buy magazines solely to take the tests to find out more about myself and then make my family or friends take them so that I could gain a little more understanding about others. It didn’t matter how insignificant it was to learn which Disney Princess my friends were most like. It was fun for me to gather more knowledge about their character (no pun intended).

Who would have known that my obsession with collecting information about others via personality tests could be utilized in a professional setting?

Connecting with Others Through Marketing

The Marketing Team at InterWorks places a high priority on representing our brand. Recently, our Global Marketing Manager, Jenny Parnell, wrote a blog about brands you stay loyal to and brands you leave behind. She mentions the InterWorks brand and how it is meant to have an impact on the audiences we serve.

Our hard work and collaborative efforts allow us to produce high quality externally-facing brand experiences for folks. Whether we do that through design, events, content or webinars, we do our best to connect with our customers on a personal and experiential level. In this, we’ve realized that if we are trying to give our best to our customers, it’s also incredibly important to place an emphasis on our connection as a team.

This can be somewhat of a challenge due to the team being spread out. While most of the marketing team resides and works out of our Tulsa office, our CMO, Dalton Parsons, works in Stillwater; the Events Team—Brittany Dunn and I—work out of Oklahoma City; and one of our communications managers, Vicky Lockett, is in the UK. While Slack and Zoom are great tools to help us stay connected, it’s sometimes a challenge for us to get to know our colleagues with whom we tend to work extremely closely on a deeper, more individual level.

Above: The U.S. members of the Marketing Team

Connecting with Each Other Through Personality

When I started working at InterWorks, I mentioned how much I loved personality tests to Jenny, and she and I came up with a goal of trying to connect the team with personality spotlights via Slack. At the time, our team was also in the process of growing at a fast pace, so as new people were added, we were able to get to know them even if we weren’t in the same office.

I would do this by taking a specific personality type in the Myers Briggs or Enneagram frameworks and give it a breakdown. This was an effort to understand and respect the differences present in our team members’ work dynamics and energy levels, allowing us to move forward a little more aware of the folks we work with, as well as create some self-awareness in the group.

Before the personality spotlights, I would give a general explanation of the test so that we had clarification on what each part of the test results meant. I had just taken a Tableau Desktop 1 course and wanted to try my hand at a viz, so we could have a visual representation of the information I was sharing with the team. Periodically, we’ve updated the viz, and before I published it, I had a LOT of guidance and help from Danny Steinmetz and Grant Eisenmenger who really polished up its rough edges!

While these spotlights were communicated through instant messaging on Slack, they were thorough and highlighted nearly everything, including fun facts, personal values, things you should never say to someone with a specific personality type, best books for a specific type to read, how those folks handled conflict and much more.

If you know anything about the Enneagram or Myers Briggs, I am an ENFJ and a 2 (a.k.a. the Helper). As a 2 and an ENFJ, I take a great deal of pride and joy in guiding others to work together to improve themselves and their community. For these reasons, it was really rewarding for me to have a chance to incorporate a passion into my work and do just that.

How Personality Insights Make a Difference

But enough about me! I reached out to my team to get their insight on why they thought this was a good exercise. Below are some responses:

  • “I think this whole exercise was beneficial for two big reasons. First off, it makes you think deeply about what really makes you tick as a person. Self-awareness is a seriously underrated advantage in working well with others. Secondly, and perhaps more importantly, it gives you deep insight into those around you. When you see what drives people beneath the surface, their aspirations and their fears, that’s when true understanding starts being built. Better understanding leads to better collaboration, and better collaboration leads to better work across the board.”
  • “I think the personality tests are a great way to understand yourself and your team members better. It is easier to empathize and work on projects with people when you have an understanding of what their perspective might be and how it may differ from your own.”
  • “I think anyone who takes a personality test for the first time is surprised by how accurate they can be. It’s always fun to find out more about yourself and know how those details help you advance and grow. Sometimes, it can also be a good view into some of your blind spots. For our team, it’s a great discussion point overall to know your personality type. Even as simple as knowing who is an introvert vs. extrovert has helped us know comfort levels in group settings. With all of the personality boxes we can put ourselves in, I think the gift from these insights is that we can remind each other that all of us have unique qualities/strengths we can apply to work together in the best ways. I’ve learned it’s all about seeing these insights as ways to know more of your applied strengths, not let them constrain you in any way. For instance, I should not say, ‘No, I can’t do that, I’m a 2!’ Instead, I might aim for ‘I should bring my strengths and perspectives as a 2 to this situation.’ Likewise, these should never be used as a form of exclusion: ‘They’re a 2, so we’ll never get along.’ Really, maybe the healthy point of view is ‘I’m really excited to hear what your point of view is here (no matter your number/type).’
  • “I’ve always loved personality frameworks, but they were especially helpful for me when walking into this team as a new hire. I immediately was able to gain some steadier footing when it came to knowing my colleagues, understanding myself and seeing how we were all able to work together, like complementary puzzle pieces. It’s been really fun to explore and discuss as a team.”

Pairing Personal Passion with Professional Work

Overall, this was an incredible way for me to blend some passion with my work. Additionally, it was an avenue for me to know team members with whom I don’t get to spend a lot of face-to-face time. My dream would be to do this for the entire company and have a chance to highlight personalities across other teams and departments. I think it was a valuable activity for our team that everyone really enjoyed.

I love that InterWorks provides opportunity for us to combine things we enjoy with our work. It makes it a lot easier to represent our brand well, and if anything, it gave me and others a chance to see that while our work styles may be different, we have the same goal: to put out the best work alongside the best people.

Above: Better understanding each other helps us forge deeper relationships. And is a big reason why we have fun together!

The post What’s Your Sign? Team Building with Personality Frameworks appeared first on InterWorks.


Moving to `zsh`, part 8 – Scripting `zsh`

Apple has announced that in macOS 10.15 Catalina the default shell will be zsh.

I will be giving a half-day ‘Moving to zsh’ class in Amsterdam on September 6. We will cover the ‘why’ and ‘how’ of zsh and show lots of practical examples how using zsh will improve your Terminal productivity. More info and sign-up on the Pro Warehouse webpage.

In this series, I will document my experiences moving bash settings, configurations, and scripts over to zsh.

This is the final article in this series. (If I ever announce an eight part series again, please somebody intervene!) However, I am quite sure it will not be the last post on zsh

All the previous posts described how zsh works as an interactive shell. The interactive shell is of course the most direct way we use a shell and configuring the shell to your taste can bring a huge boost in usefulness and productivity.

The other, equally, important aspect of a shell is running script files. In the simplest perspective, script files are just series of interactive commands, but of course they will get complex very quickly.

sh, bash, or zsh?

Should you even script in zsh? The argument for bash has been that it has been pre-installed on every Mac OS X since 10.2. The same is true for zsh, with one exception: the zsh binary is not present on the Recovery system. It is also not present on a NetInstall or External Installation System, but these are less relevant in a modern deployment workflow, which has to work with Secure Boot Macs.

If you plan to run a script from Recovery, such as an installr or bootstrappr script or as part of an MDS workflow, your only choices are /bin/sh and /bin/bash. The current /bin/bash binary is 12 years old, and Apple is messaging its demise. I would not consider that a future proof choice. So, if your script may run in a Recovery context, i would recommend /bin/sh over either /bin/bash or /bin/zsh

Since installation packages can be run from the Recovery context as well, and you cannot really always predict in which context your package will be used, I would extend the recommendation to use /bin/sh for all installation scripts as well.

While sh is surely ubiquitous, it is also a ‘lowest common denominator’, so it is not a very comfortable scripting language to work in. I recommend using shellcheck to verify all your sh scripts for bashisms that might have crept in out of habit.

When you can ensure your script will only run on a full macOS installation, zsh is good choice over sh. It is pre-installed on macOS, and it offers better and safer language options than sh and some advantages over bash, too. Deployment scripts, scripts pushed from management systems, launch daemons and launch agents, and script you write to automate your admin workflows (such as building packages) would fall in this category.

You can also choose to stick with bash, but then you should start installing and using your own bash 5 binary instead of the built in /bin/bash. This will give you newer security updates and features and good feeling that when Apple does eventually yank the /bin/bash binary, your scripts will keep working.

Admins who want to keep using Python for their scripts are facing a similar problem. Once you choose to use a non-system version of bash (or python), it is your responsibility to install and update it on all your clients. But that is what system management tools are for. We will have to get used to managing our own tools as well as the users’ tools, instead of relying on Apple.


To switch your script from using bash to zsh, you have to change the shebang in the first line from #!/bin/bash to #!/bin/zsh.

If you want to distinguish your zsh script files, the you can also change the script’s file extension from .sh to .zsh. This will be especially helpful while you transfer scripts from bash to zsh (or sh). The file extension will have no effect on which interpreter will be used to run the script. That is determined by the shebang, but the extension provides a visible clue in the Finder and Terminal.

zsh vs bash

Since zsh derives from the same Bourne shell family as bash does, most commands, syntax, and control structures will work just the same. zsh provides alternative syntax for some of the structures.

zsh has several options to control compatibility, not only for bash, but for other shells as well. We have already seen that options can be used to enable features specific for zsh. These options can significantly change how zsh interprets your scripts.

Because you can never quite anticipate in which environment your particular zsh will be launched in, it is good practice to reset the options at the beginning of your script with the emulate command:

emulate -LR zsh 

After the emulate command, you can explicitly set the shell options your script requires.

The emulate command also provides a bash emulation:

emulate -LR bash 

This will change the zsh options to closely emulate bash behavior. Rather than relying on this emulation mode, I would recommend actually using bash, even if you have to install and manage a newer version yourself.

Word Splitting in Variable Substitutions

Nearly all syntax from bash scripts will ‘just work’ in zsh as well. There are just a few important differences you have to be aware of.

The most significant difference, which will affect most scripts is how zsh treats word splitting in variable substitutions.

Recap: bash behavior

In bash substituted variables are split on whitespace when the substitution is not quoted. To demonstrate, we will use a function that counts the number of arguments passed into it. This way we can see whether a variable was split or not:

#!/bin/bash export PATH=/usr/bin:/bin:/usr/sbin:/sbin  function countArguments() {     echo "$  {#@}" }  wordlist="one two three four five"  echo "normal substitution, no quotes:" countArguments $  wordlist # -> 5  echo "substitution with quotes" countArguments "$  wordlist" # -> 1 

In bash and sh the contents of the variable split into separate arguments when substituted without the quotes. Usually you do not want the splitting to occur. Hence the rule: “always quote variable substitutions!”

zsh behavior: no splitting

zsh will not split a variable when substituted. With zsh the contents of a variable will be kept in one piece:

#!/bin/zsh emulate -LR zsh # reset zsh options export PATH=/usr/bin:/bin:/usr/sbin:/sbin  function countArguments() {     echo "$  {#@}" }  wordlist="one two three four five"  echo "normal substitution, no quotes:" countArguments $  wordlist # -> 1  echo "substitution with quotes" countArguments "$  wordlist" # -> 1 

The positive effect of this is that you do not have to worry about quoting variables all the time, making zsh less error prone, and much more like other scripting and programming languages.

Splitting Arrays

The wordlist variable in our example above is a string. Because of this it returns a count of 1, since there is only one element, the string itself.

If you want to loop through multiple elements of a list

In bash this happens, whether you want to or not, unless you explicitly tell bash not to split by quoting the variable.

In zsh, you have to explicitly tell the shell to split a string into its components. If you do this naïvely, by wrapping the string variable in the parenthesis to declare and array, it will not work:

wordlist="one two three" wordarray=( $  wordlist )  for word in $  wordarray; do     echo "->$  word<-" done  #output ->one two three<- 

Note: the for loop echoes every item in the array. I have added the -> characters to make the individual items more visible. In the subsequent examples, I will not repeat the for loop, but only show its output. So the above example will be shortened to:

wordarray=( $  wordlist ) ->one two three<- 

There are few options to do this right.

Revert to sh behavior

First, you can tell zsh to revert to the bash or sh behavior and split on any whitespace. You can do this by pre-fixing the variable substitution with an =:

wordarray=( $  {=wordlist} ) ->one<- ->two<- ->three<- 

Note: if you find yourself using the = frequently, you can also re-enable sh style word splitting with the shwordsplit option. This will of course affect all substitutions in the script until you disable the option again.

setopt shwordsplit wordarray=( $  wordlist ) ->one<- ->two<- ->three<- 

This option can be very useful when you quickly need to convert a bash script to a zsh script. But you will also re-enable all the problems you had with unintentional word splitting.

Splitting Lines

If you want to be more specific and split on particular characters, zsh has a special substitution syntax for that:

macOSversion=$  (sw_vers -productBuild) # 10.14.6 versionParts=$  {(s/./)macOSVersion} ->10<- ->14<- ->6<- 

If you want to split on a newline character \n the syntax is slightly different:

citytext="New York Rio Tokyo"  cityarray=( $  {(ps/\n/)citytext} ) ->New York<- ->Rio<- ->Tokyo<- 

Since newline is a common character to split text on, there is a short cut:

cityarray=( $  {(f)citytext} ) 

Since the newline character is a legal character in file names, you should use zero-terminated strings where possible:

foundDirs=$  (find /Library -type d -maxdepth 1 -print0) dirlist=$  {(ps//)foundDirs} 

Again, there is a shortcut for this:

dirlist=$  {(0)foundDirs} 

Array index starts at 1

Once you have split text into an array, remember, that in zsh array indices start at 1:

% versionList=( $  {(s/./)$  (sw_vers -productVersion)} ) % echo $  {versionList[1]} 10 % echo $  {versionList[2]} 14 % echo $  {versionList[3]} 6 

If you think this is wrong and absolutely require a zero-based index, you can set the KSH_ARRAYS shell option:

% setopt KSH_ARRAYS % echo $  {versionList[0]} 10 % echo $  {versionList[1]} 14 % echo $  {versionList[2]} 6 % echo $  {versionList[3]}  


Switching your scripts from bash to zsh requires a bit more work than merely switching out the shebang. However, since /bin/bash will still be present in Catalina, you do not have to move all scripts immediately.

Moving to sh instead of zsh can be safer choice, especially for package installation scripts.

In zsh, there always seems to be some option to disable or enable a particular behavior.

This concludes my series on switching to zsh on macOS. I hope you found it helpful.

After having worked with zsh for a few weeks, I already find some of its features indispensable. I am looking forward to discovering and using more features over time. When I do, I will certainly share them here.

Scripting OS X