Compile PHP extensions: GNUPG as an example

I have this error on my server:
PHP Startup: Unable to load dynamic library '/usr/lib/php/20160303/'

And this is how I fixed it:

Short answer

Remove old php dev and install a new one that fits your php version:

sudo pecl uninstall gnupg
sudo apt-get remove php5.6-dev
sudo apt-get install php7.1-dev
sudo pecl install gnupg


Extension dir for php 5.6 is `/usr/lib/php/20131226` and extension dir for php 7.0 is `/usr/lib/php/20151012` as this command shows:

php -r "print phpinfo();" | grep "extension_dir"

Pecl installs gnupg in `/usr/lib/php/20131226/` because the pecl was installed when php 5.6 is enabled

pecl list-files gnupg


PHP 7.0 uses a different extension directory than where the gnupg is installed.

**First try which didn’t work**:

Create symlink for inside the php 7.0 extension directory that points to inside php 5.6

sudo ln -s /usr/lib/php/20131226/ /usr/lib/php/20151012/

**Results in**:

Warning: PHP Startup: gnupg: Unable to initialize module
Module compiled with module API=20131226
PHP compiled with module API=20151012

**Second try which also didn’t work**:

1. Uninstall pecl extension: `sudo pecl uninstall gnupg`
2. Activate php v 7.0
3. Install gnupg again: `sudo pecl install gnupg`

Gives same compiling error.

**Other try and error**:

Install a compiled version of the gnupg that works with php 7.0: see php [docs here][1]

Download latest pecl extension source from

Find which configuration file is used by your PHP 7.0 installation

php --ini
Configuration File (php.ini) Path: /etc/php/7.0/cli

Compile the pecl extension:
cd {downloaded extension folder}
./configure –with-php-config=/home/vagrant/Downloads/php-src-PHP-7.0/scripts/php-config

**Check if gnupg is installed**

php -r ‘var_dump(function_exists(“gnupg_decrypt”));’;

Compile php dev source
Create configuration build: ./buildconf
Init a config: ./configure –prefix=/usr/local/php7/7.0.0 –localstatedir=/usr/local/var –sysconfdir=/usr/local/etc/php/7 –with-config-file-path=/usr/local/etc/php/7 –with-config-file-scan-dir=/usr/local/etc/php/7/conf.d –mandir=/usr/local/php7/7.0.0/share/man

You can then check out the branch you want to build, for example:

PHP 5.4: git checkout PHP-5.4
PHP 5.5: git checkout PHP-5.5
PHP 5.6: git checkout PHP-5.6
PHP 7.0: git checkout PHP-7.0
PHP HEAD: git checkout master

Using Putty to connect to an SHH tunnel

If in Linux you use this command to make a tunnel:

ssh -p 32642 -L 3308:something:3306 -N -i ~/.ssh/id_rsa

Then this is how you translate the above command into a Putty configuration session.

Download Putty and install it:

Configure Putty:
  1. In the “Session” category:
    1. Create a new session by typing its name in “Saved Sessions”.
    2. Fill “Host name or IP address” ( and “Port” (32642) and make “connection type” SSH
  2. Go to “Connection” => “Data”:
    1. Set “Auto-login username” (tunnel)
  3. Go to “Connection” => “SSH”:
    1. Check “Don’t start a shell or command at all”
  4. Go to “Connection” => “SSH” => “Auth”:
    1. Click on “Browse..” and load a ppk key (or convert other private keys using the included Puttygen software)
  5. Go to “Connection” => “SSH” => “Tunnels”:
    1. Check “Local ports accept connections from other hosts”
    2. Fill “Source port” (3308) & “Destination” (something:3306) and click on “Add”
  6. Go to “Session” and save this config and click on “Open” to start the session

Test if this port is connected by running CMD as admin and type netstat -a -b you should see a that port 3308 used.

Next time you want to open a tunnel, open Putty and double click on the name of your saved session and voila!

Connecting to MySQL through SSH tunnel

In this post we see how to connect to a MySQL server using SSH tunnel and local forwarding.

This command will create a tunnel in the background:

ssh -p 32642 -L 3308:example:3306 -N -i ~/.ssh/abdel -f
mysql -h -P 3308 -u user -p db

This is what each parameter means:

-p port
Port to connect to on the remote host. This can be specified on
a per-host basis in the configuration file.

-L [bind_address:]port:host:hostport
Specifies that the given port on the local (client) host is to be
forwarded to the given host and port on the remote side. This
works by allocating a socket to listen to port on the local side,
optionally bound to the specified bind_address. Whenever a con-
nection is made to this port, the connection is forwarded over
the secure channel, and a connection is made to host port
hostport from the remote machine. Port forwardings can also be
specified in the configuration file. IPv6 addresses can be spec-
ified with an alternative syntax:
[bind_address/]port/host/hostport or by enclosing the address in
square brackets. Only the superuser can forward privileged
ports. By default, the local port is bound in accordance with
the GatewayPorts setting. However, an explicit bind_address may
be used to bind the connection to a specific address. The
bind_address of “localhost” indicates that the listening port be
bound for local use only, while an empty address or ‘*’ indicates
that the port should be available from all interfaces.

-N Do not execute a remote command. This is useful for just for-
warding ports (protocol version 2 only).

-f Requests ssh to go to background just before command execution.
This is useful if ssh is going to ask for passwords or passphrases,
but the user wants it in the background. This implies -n.
The recommended way to start X11 programs at a remote site
is with something like ssh -f host xterm.

-i identity_file
Selects a file from which the identity (private key) for public
key authentication is read. The default is ~/.ssh/identity
for protocol version 1, and ~/.ssh/id_dsa, ~/.ssh/id_ecdsa,
~/.ssh/id_ed25519 and ~/.ssh/id_rsa for protocol version 2.
Identity files may also be specified on a per-host basis in the
configuration file. It is possible to have multiple -i options
(and multiple identities specified in configuration files).
ssh will also try to load certificate information from the
filename obtained by appending to identity filenames.


To check if the command was successful run: sudo netstat -tulpn | grep "3308"

You should see something like:

$ sudo netstat -tulpn | grep "3308"
tcp 0 0* LISTEN 14634/ssh
tcp6 0 0 ::1:3308 :::* LISTEN 14634/ssh

Using an Ansible role

To list jobs interacting with ssh: initctl list | grep ssh

To stop the service:
stop autossh-tunnel-client


My own Ansible role:

- name: Install package
name: ssh
state: present
become: yes

- name: Copy key file(s)
src: "{{ item.src }}"
dest: "{{ item.dest | default(item.src | basename) }}"
owner: "{{ item.owner | default('root') }}"
group: "{{ | default(item.owner) | default('root') }}"
mode: "{{ item.mode | default('0600') }}"
validate: 'echo %s'
with_items: "{{ params['ssh_tunnel'].keys_map }}"
become: yes

- name: Run SSH tunnel on background
command: ssh -f "{{ params['ssh_tunnel'].user }}"@"{{ params['ssh_tunnel'].host }}" -p "{{ params['ssh_tunnel'].port }}" -L "{{ params['ssh_tunnel'].forward }}" -N -i "{{ item.dest | default(item.src | basename) }}"
with_items: "{{ params['ssh_tunnel'].key_map }}"
become: yes


- src: '../../../private/id_rsa'
dest: ~/.ssh/id_rsa
host: ''
forward: '3308:some:3306'
port: 32642
user: tunnel


Check as well: Using Putty to connect to an SHH tunnel

Vagrant: Relative paths in Ansible roles

The default path to the current working directory for any role is the role directory itself!

So if you reference a file inside of a role task: lets say the copy module for example, and the file is not in that role directory then you have to use parent path .. /.

Directory structure:

├── devenv/
│   └── ansible
│       └── roles
│           └── autossh
│               └── tasks
│                   └── main.yml
├─────── private/
│        └── myfile.txt
└────── Vagrantfile

In the case the cwd is autosh and the relative path to the file is: ../../../private/myfile.txt

You cannot use relative paths that are outside the Vagrantfile folder: when you do vagrant up it maps the directory to one in the VM and there it references the files.. so no files outside that directory will work if it doesn’t exist already in the VM.

Plentymarkets: the worst shopping system ever!

At my work we deal often with shopping systems to get orders or customer data and sent it to our API. Most of time using the different shopping systems APIs is easy and quick.. Except for Plentymarkets: it is a nightmare!!!

Plentymarkets newsletter image

While I don’t want to spend more time writing about my experience about this shopping system (because I spent already too much time with it trying to fix small problems) I will just make a quick bullet list of bugs/issues I had with them:

  1. The keep changing the API so often: the changes are big that it breaks your code!
  2. Their documentation is shit and most of it is auto generated.
  3. They send you emails with non working links!
  4. In order to change the shop admin password, you need to call them!! They don’t have a reset password link!
  5. In other shopping systems you have access to a demo account where you can test your API calls, with Plentymarkets they only give you a limited 1 month account that you have to pay for afterwards!
  6. They are advertising the use of REST API instead of SOAP but it never happened! The docs are confusing :/
  7. The admin interface UI and design is awful and not user friendly. Serious shop system some some time and money to get a better UX, unfortunately, it seems not the case with Plentymarkets.
  8. While using a PHP framework is a good idea, Plentymarkets is using Laravel to power up (at least) their REST API: Larevl is based on Symfony, which is much more greater.
    However when I was playing with the rest/orders endpoint I got this uncatched error exception:

    "message": "Type error: Argument 1 passed to Illuminate\\Validation\\Factory::make() must be of the type array, integer given, called in /var/www3/plenty/stable7_a/pl/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php on line 219",
    "status_code": 500
  9. TBC..

A little history about Plentymarkets:
It started out as a contract job for a few eBay PowerSellers. The development began in 2001 and initially focused on providing an interface to eBay, the ability to process orders and an online store. Back then, the software was still called plentyShop.
It developed into a shop system very quickly and then it became an all-in-one solution for managing important e-commerce processes such as B2B and B2C, checkout, content management, invoicing, stock management, after sales management, fulfillment and returns.

Hide password on MinTTY and Cygwin

If you want to use SMB folder sharing for vagrant on a windows machine, you will need to run the CLI as administrator and then provide the login and password. The problem is that MinTTY and Cygwin do not support the no-echo TTY mode.

To fix this I use this code snippet:

# Capture username and password for smb synced folders (Windows)
   puts "\e[36mDue to MinTTY and Cygwin not supporting the no-echo TTY mode"
   puts "it is necessary to request your account username and password"
   puts "at this stage in order to correctly setup SMB shared folders.\e[0m"

   if !parameters['smb_username']
      print "\e[35mEnter username:\e[0m "
      smb_username = STDIN.gets.chomp
      puts "\e[32mUser name is already supplied in parameters.\e[0m"
      smb_username = parameters['smb_username']

   if !parameters['smb_password']
      # 8m is the control code to hide characters
      print "\e[35mEnter password:\e[0m \e[0;8m"
      smb_password = STDIN.gets.chomp
      # 0m is the control code to reset formatting attributes
      puts "\e[0m"
      puts "\e[32mUser password is already supplied in parameters.\e[0m"
      smb_password = parameters['smb_password']


I have this vagrant setup where I map my shared folder to a folder where I have all my projects. In windows 10 I got this strange problem from Chrome showing


After many researches I found out the problem was on the hosts file;

I just had to write each entry in a new line and save; that stupidly fixed it!

Add subdomain to a Let’s Encrypt SSL certificate

First of all, add an entry to the DNS zone; you can do this in your host panel.

Pointer records = A

Target = IP of the server


Remove existing one with this command:

sudo rm -rf /etc/letsencrypt/live/

sudo rm /etc/letsencrypt/renewal/

sudo rm -rf /etc/letsencrypt/archive/

Stop server:

sudo service nginx stop

Create a new one certificate:

cd /opt/letsencrypt
./letsencrypt-auto certonly --standalone

Then add all domaines there separated by comma;

Restart server:

sudo service nginx restart

Update nginx sites

sudo rm /etc/nginx/sites-enabled/

sudo ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/

Disable Xdebug when installing Composer packages

Find  the folder where composer is installed:

which composer

Open the composer file and replace:

php "${dir}/composer.phar" $*


php -n -d extension=C:/wamp/bin/php/php7.0.7/ext/php_openssl.dll -d extension=C:/wamp/bin/php/php7.0.7/ext/php_pdo_sqlite.dll -d extension=C:/wamp/bin/php/php7.0.7/ext/php_mbstring.dll "${dir}/composer.phar" $*

The parameters we passed to php are:

  • -d foo[=bar] => Define INI entry foo with value ‘bar’
  • -n => No configuration (ini) files will be used

I passed to the -d parameter the extension that I need php to load whenever it executes the composer command, feel free to change those to your needed extensions.