Set the upload limit on a multisite

Last week, a colleague was asking, how you set the upload limit for WordPress. He tried different things, but still the limit was not increased. Let’s take a look, at what you have to do, to raise the limit.

Checking the current upload limit

One of the easiest ways to check the current limit is to upload a new media item. When you navigate to the media library, you will see the current limit:

The Media Library upload showing a "Maximum upload file size: 63 MB"
The Media Library upload showing a “Maximum upload file size: 63 MB”

Here you can see, that we can upload files up to 63 MB. There is a second place in the WordPress dashboard, where you can check the limit, at Tools > Site Health > Info > Media Handling:

The "Media Handling" info of the Site Health Info showing a max. upload size of 64 MB.
The “Media Handling” info of the Site Health Info showing a max. upload size of 64 MB

On this screen, it displays 64 MB. But how do we increase the size, if we need to upload larger files?

Increase the limit on the server level

In order to upload larger files, the server needs to accept them. This is what the “Max size of post data allowed” is telling you. To set those values, you usually have to change the php.ini file:

post_max_size = 128M
upload_max_filesize = 128M

I wrote a blog post about how you can make those changes, without losing them on an update. Depending on the PHP interpreter you use, you might have to restart it. Once we have changed this value, let’s check it in our installation:

The "Media Handling" info of the Site Health Info showing a max. upload size of 128 MB.
The “Media Handling” info of the Site Health Info showing a max. upload size of 128 MB.

So now we should be able to upload files up to 128 MB, right? Let’s find out in the media library:

The Media Library upload showing a "Maximum upload file size: 63 MB"
The Media Library upload showing a “Maximum upload file size: 63 MB”

No, we can’t! It’s still showing only 63 MB. But why? The issue here is, that we are on a multisite, and there is another option we have to change.

Setting the upload limit for a multisite

Navigate to the “Network Settings” and then almost all the ways down to the “Upload Settings”. Here you will still find the lower value:

The "Upload Settings" in the "Network Settings" still showing 64000 KB.
The “Upload Settings” in the “Network Settings” still showing 64000 KB

Let’s set 128000 KB here, save the settings and go back to the media library file uploader:

The Media Library upload showing a "Maximum upload file size: 125 MB"
The Media Library upload showing a “Maximum upload file size: 125 MB”

Now we see our increased file size. If we want to have 128 MB, we need to multiply the value we put into the setting with 1.024, but this should be good enough.


While you might have increased the upload limit on many servers and WordPress installs, and never had an issue. On a multisite, there is one additional setting you have to be aware of.

How about you? Are you using multisite installations? Why, or why not? Have you also found some strange differences, you sometimes forget about?

Different methods to debug PHP code

This week, I was asked for help setting up Xdebug with PhpStorm on a Linux laptop in combination with Docker. I might write about that in an upcoming blog post, but today, I want to focus on different methods to debug code in PHP.

1. echo, print_r, var_dump – the quick but ugly way

Anyone who ever developed something in PHP has used one of the “output functions”. With echo you can output a generic value like a string or number. For arrays and objects, you would use print_r or var_dump.


  • Simple: The methods are easy to use. You don’t have to install anything to use them.
  • Quick insight: When you just want to output a value at a certain line, it’s easy to get the current value at that line.


  • Output limitations: The methods are rather simple. You only get the value of that one variable, but no “context”. As the output is often rendered into an HTML document, you might have to wrap it in a <pre> tag for array/objects or you have to check them in the source code view.
  • Intrusive: When you output the variables, you do might do that while rendering a template. This means that your layout/design gets distorted.
  • Insecure: When you use this technique on a live website, the output is visible to any visitor. Not only does this look like an error, it was also be a security issue, when the content of the data you debug should not be visible.
  • Needs cleanup: Even if you only use this technique while developing locally, you always have to remember this code, before you deploy the code or commit and push it into version control. It’s easy to miss some lines which then end up on a live website.

2. error_log, file_put_contents – logging to a file

Instead of a direct output on the screen, you could write the debugging output into a file. This has some advantages, but some other drawbacks:


  • Separation of concerns: Logging debugging information to a file keeps the application code free of debugging output. You could even introduce your own debugging function as wrapper to the error_log or file_put_contents functions and check inside of this function, if the “debug mode” is active.
  • Persistent records: If you have this “debug mode” activated, you can save the logged information into the file over a longer period of time and check the file for potential issues. It also enables you to debug requests from other people, so you might be able to find issues, you don’t “see” youself.
  • Not distorted output: Your output on the website keeps clean, as any debugging information is only written to a file.


  • Manual inspection: You don’t get the debug information instantly. If you run this on a live website, you might even see the debug information of another person. Running a tail command on your file might help with a more “real time” debugging.
  • Harder to debug: If you use a common debug file for all functions, you will get all debug information without any “context”, so when want to identify the file/line a specific log message was written, you might have to prefix them with the __FILE__ and __LINE__ and maybe even add the date/time, so you know then the log entry was written.
  • Overhead: Logging information to a file can decrease the performance of your site, especially when you debug a lot of data. You should also check the file size of the debug file from time to time and make sure to deactivate the debugging mode, as you can easily fill up all remaining space on your server with a huge log file.
  • Insecurity: Yes, also using a debug file can cause a security issue, in case the log file is publicly available. If it is not publicly available, then debugging with a log file requires you to have access through a terminal or by downloading it. WordPress for example stores the debug.log in the wp-content folder and exposes it to anyone visiting the site. But you can change the path, to make this more secure.

3. Debug plugins – getting the best of both variants

For WordPress, I usually always use the Query Monitor plugin. This plugin offers multiple actions to debug variables. One of them is the qm/debug action.


  • No direct output: Using this action, you don’t output the logged information into the page, so the site does not get distroted.
  • Security: You usually only see the debug information of Query Monitor (and the hook), if you are an administrator of the site. But you can even use it (with the help of a cookie), while logged out.
  • No debug file needed: It does send the debug information with the response, and does not store a file on the server, so the disadvantages of the previous method don’t apply here.


  • Debug code in your application code: Like with the other methods, you still have to write debug code into your application code. You would then also deploy this code on a live website or commit and push it into a repository. So while this might be OK for “unpublished” code, you would rather not want to have it in a plugin/theme you have published. So before releasing a new version of the plugin/theme you would need to remove this debug code.
  • Cannot debug broken pages: As this debugging methods requires the Query Monitor plugin, you cannot debug a fatal error, that would not allow the Query Monitor to display that error.
  • Only your errors: You cannot use this method to debug issues another person is having with your site, and you probably don’t want to give them the permissions to use Query Monitor themselves.

4. Xdebug – next level of debugging

If you haven’t heard of Xebug and/or never used it: use it right after finishing reading this blog post! For me, it is the best tool to debug complex issues. Xdebug is a PHP extension, that needs to be installed and activated. You also need an IDE like PhpStorm or VS Code to use it, but then it can really help you find these hard to debug issues.


  • No additional code needed: Instead of writing any debugging functions into your code, you rather set a “breakpoint” in your IDE to a specific line.
  • Debug all the things: Once a breakpoint is reached, you can debug any variable! So you are not limit to a single variable to debug, but you can see the value of all of them. You can also see the “call stack”, so every function that was called before reaching this point. You can also step back to these functions and inspect the parameters they were called with.
  • Only investigate special cases: Let’s say your code only breaks in certain cases, so when a variable has a specific value. In this case, you can use a conditional breakpoint that only stops, when this condition is met.
  • Test “what if…”: So you found an issue with a variable and its value, and wonder if the code would run as expected with a different value? Well, when you are at a breakpoint, you can overwrite the current value of a variable and then resume, to see what would happen.


  • Hard to setup: If you have never used Xdebug before, it might be hard to set it up. You have to install and activate the PHP extension on the machine you are running the code at, and you have to configure your IDE. When using it with Docker, you also have to make sure that the PHP container can “communicate” with your IDE. While it has become a lot easier and PhpStorm nowadays needs (almost) zero configuration, getting it working for the first time might not be easy. I usually use it in combination with DDEV and now know how to get it running quickly. But your setup might differ.
  • Might break your request: As you “stop” the request while debugging, it might be possible that your site will not render, once you stepped through your code. That is usually the case if you use nginx as the webserver, as you will get an error 500 because the “PHP backend” times out.
  • Not really usable on live environments: While Xdebug is great for development on a local environment, it is not the best tool to be used on a live website. Xdebug has a huge performance impact on the system. Also, due to the fact that Xdebug has to “communicate” with your IDE, it might be hard to impossible to get a setup that works with your IDE and a live site.


There are different methods to debug PHP code, and all oft of them have their pros and cons – even more than I have mentioned in this blog post. In some cases, a quick debug with echo might be all you need. But there have been some issues, I would never have been able to find without the help of Xdebug. So I would highly recommend finding a setup that works for you.

I asked some people about their debugging methods. Around 40% use method 1 frequently, some 5% use method 2 and “only” 55% have used Xdebug before. I hope I can find a way to convince the other 45% to finally start using Xdebug in as their debugging tool.

How about you? Have you used Xdebug? Do you use other methods I have not mentioned, that really work well for you? Then please leave a comment.

WordCamp Leipzig 2023 – Smaller in size but equally amazing!

Yesterday, I visited WordCamp Leipzig. It was the first in-person event in Germany since 2019. It was a WordCamp, but also special in some ways.

Taking a train to Leipzig

When attending a WordCamp, I usually have to book trains (or flights) as well as accommodations. For this WordCamp tough, I just left my apartment at a little after 7:00, walked less than 10 min to the train, took an ICE for around 1:40h, picking up some quick breakfast and a coffee at Leipzig central station, then a tram to the venue, and finally I arrived the WordCamp at just about 2h after leaving my apartment. It was almost as if it was a local WordCamp in Berlin.

Meeting the local community

This WordCamp has always been targeted for the local community of Leipzig. Some people travelled a bit longer than I did, at least one even from another country. But I still had the chance to meet some new community members, I have never met before, as well as some, who regularly attended the WP Meetup Berlin via Zoom.

A single track schedule with invited speakers

I usually write a bit about the specific sessions I have visited at a WordCamp. But since this one had only one track, it was simple: I attended all of them 🙂

All speakers were invited by the organizing team. Some of the topics were not amongst the ones I would usually join at a WordCamp with multiple track, but I enjoyed all of them. Some with more in-depth knowledge transfer, some more broad.

The Berlin community member Maja also gave a talk about accessibility. While I knew many of the things already, some of the new regulations coming in 2025 were new to me:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Translation: @MajaBenke explains to all attendees, why a #website must be #accessible! Already today! But especially in 2 years, for even more sites. #WCLE23

No lunch!

WordCamp Leipzig wanted to be “as minimalistic as possible”. That’s why we were asked to find ourselves something to eat for lunch. The venue was well located for this idea. Within 100 m there were plenty of food options for every taste. I joined a group for some (vegetarian) burger, and we talked a lot about plugins, settings/options and other developer topics.

But it was not, that the organizing team was not at all catering for us. We could have free drinks (non-alcoholic) and mate or cola helped to stay fit for the afternoon schedule. In the afternoon, we also had cake and muffins. In between talks, we often went outside to get some fresh oxygen and talk.

A long and short day

Attending 7 talks in a row without the chance to do something else, like talking to sponsors on a larger WordCamp, the day felt long. But also quite short, because all of a sudden the last talk was finished, and it was time for the closing remarks.

After thanking the organizing team (of 5 people) as well as the global and local sponsors, the team announced, that there will be a WordCamp Leipzig in 2024 as well:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Translation: And #WCLE2024 will take place in 2024 in… Leipzig ?

The organizers asked everyone to leave and suggested, that we find some people to go somewhere for some socializing. But while standing outside, one of the organizers informed us, that the venue agreed to host us for some more hours. And since the free drinks were still not empty, people could then buy beer and other drink and someone suggested to just order pizza, we stayed for an “after party”:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Translation: Definitely no afterparty #WCLE23

I spent another 4h talking to attendees about various topics, and someone finally explained to me, how Instagram works ?

Time to go home

I took a tram back to the train station and fortunately, I was able to share the train ride back with Heiko, who lives really close and who booked the same train. Just before midnight, I was finally home after this WordCamp, which really felt like a local one to me.

Was it really a WordCamp?

Last week, WP Tavern wrote about the new WordCamp formats, the community team is experimenting with, and WordCamp Leipzig was one of the events mentioned. Some people on Twitter started to argue, that this is not a WordCamp, but a meetup.

In my opinion, it was very much a WordCamp. If attended other WordCamps with just a single track, and also ones, where speakers had been invited. Not having lunch, was not an issue, but a great opportunity to connect to the other attendees and everyone could have the food they want.

So what else would people expect to call this a WordCamp and not a “full day meetup”?

A new event format for more communities

I’ve already heard that the British community is thinking about organizing a similar event. It would have more attendees and probably lunch, but also at most a low-key after party. I think it’s an amazing format for small or new communities to get started in organizing their first event.

This was indeed, with the exception of the “unofficial” WordCamp Jena back in 2009, the very first WordCamp in the former East of Germany. Seeing that the organizing team already decided, that they will do it again, is an amazing sign! And speaking of the team, they did a great job to make everyone feel welcomed. So, a big thanks to all the organizers!

Would I do an event like this as well?

I was thinking about something like this for Berlin. We even somehow tried to have it in 2015, but then the WordCamp became bigger and more “traditional” than intended. I don’t have plans to organize a WordCamp in Berlin this or next year, but I might want to try one of the new format than as well. But before having a WordCamp, I would really like to organize a do_action: Charity Hackathon in Berlin!

WordCamp Europe 2023 – A different perspective on the biggest WordPress event

I’m writing this blog post while I’m still in Greece. This year, I experienced the WordCamp in a different role. In 2017, I’ve joined the organizing team and continued to organize all in-person events until last year. But for the event in Athens, I had a different role.

My first trip to Greece

As so often in the past years, I got the chance to visit a new country through a WordCamp. This was my very first trip to Greece and therefore also my first visit to Athens. It started on Friday two weeks ago. We spent some time sightseeing. Visiting the Acropolis was a must for me, and the experience as worth it! But Athens has a whole lot more to offer. Not only ancient and historic sites, but also great food.

Working remotely

Since joining Inpsyde in October, I now have the freedom to work from all around the world. And since Athens is only 1 hour ahead of Berlin, it was easy to get some work done from Monday-Wednesday. Some other Inpsyder arrived Wednesday evening, and we met for a nice dinner.

Thursday: Contributor Day

Just like in previous edition, WordCamp Europe started with the Contributor Day. I had to organize some things in the morning, so I also arrived after the groups were already found. I’ve joined the “Meta + WordCamp” table, trying to work a bit on the ticket I’ve started back at WordCamp Asia in February. But since I’ve been invited to talk on a panel, I also took some time to discuss the topics with two other panelists. Right after that I had to leave, to change the hotel. So over all, the Contributor Day was not as effective as I hoped it would be.

In the evening, I was invited to “The Social” as a speaker. It was a good chance to meet some old friends and make some new ones.

Friday: The first conference Day

Even though I was 10 min late, I didn’t miss the opening remarks. Wendie, the Volunteers Team Lead, dressed up as a unicorn, to prepare the audience for the opening remarks:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Right after the opening remarks, I went to the first talk.

Test instead of guessing – generate more leads through growth hacking

This talk was given by my colleague Viola. Even though it’s not something I have to deal with in my job, it is a fascinating topic and Viola prepared it perfectly! You could feel that she burns for that topic, and you would not believe, this was her first talk at a WCEU (or any other event that size).

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

After this first slot, I met again with the other panelists, and we finalized our topics for Saturday we want to talk about and also prepared some answers for questions, that might come up. After lunch, I attended a round of three lightning talks about AI.

Take your WordPress website to another level using AI translation

Dario gave a brief introduction into the issue of translations and why it’s important to translate a site into different languages, as it extends your potential customer base. He also talked about the different solutions there are right now to translate a WordPress site, and why it’s crucial for those solutions to integrate translations AIs.

Innovating inclusion: harnessing AI for accessibility

The second lightning talk held by Sarah focussed on accessibility and how AI can help to improve it. She showed some tools that can automatically test for accessibility issues of your website. She also mentioned a service that provides a WordPress plugin, but it is using an accessibility overlay, something I personally don’t recommend, as it most often can’t solve accessibility issues successfully.

Content creation with the help of AI

The final lightning talk of this round was held by the German community member, Silvio. I think it was his first WCEU talk, and you could tell he was nervous. But he shows a fascinating case study about an experiment, where he had a human and an AI writing about the same topics and comparing the conversion rates of the two pages, in which the AI beat the human. I hope this is not going to be a trend.

Women and non-binary folx of WordPress

After these talks in track 1, I rushed into track 2 to watch a panel with some amazing folx from the WordPress community, talking about their personal experiences as women or non-binary community members. There were some great ideas, one of them regarding Q&A after talks. They proposed, that they should be optional, and I really like that idea. I hope some upcoming WordCamps will experience with that.

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

You say you support women in tech

The last talk of the day was held by Amy in which she shared some truly personal stories of being a single mom, woman in IT who also experienced domestic violence. She gave some really good tips for all companies that really want to support women in tech. This is the kind of talks I want to see at a flagship event.

In the evening, I was invited to multiple parties, but I only managed to join the “Pride” party, co-organized by Yoast and which I also attended in Bangkok.

Saturday: the second conference day

I started my second day a bit later and used the time before my panel to visit some sponsors and meet some more people. Then it was about time to get ready for my first panel (talk) on a WordCamp Europe.

WCEU Globals: future of WCEU

I shared the stage with Lesley, Taeke, Rocío, Moncho and Tess, the former Global Leads of WCEU 2020 – 2022. Unfortunately, Jonas couldn’t join us in Athens on stage. We talked about many different aspects of organizing an event this size and some of the things we would like to see in the future. I still don’t know if I would join a future organizing team, but I am open to become a mentor for future generations of organizers, just as all the other Global Leads.

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Right after the panel, we all went outside for the obligatory family photo. Look how many people attended this year’s event – and made it on time for the photo 😉

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

After lunch, I mainly talked to other attendees and visited some more sponsors. Somehow I almost forgot to collect any swag. Then it was time for the final session.

Variations on a theme: 20 years of WordPress

Following the tradition of WordCamp Europe, Matt also had a session. He was joined on stage by Josepha and Matías. They talked about Gutenberg phases 3 and 4, the Community Summit, and showed some fascinating things that are coming in the future. There was also enough time for attendees to ask some general questions. When the question around multilingual came up, which is only scheduled for phase 4, the audience was asked, who does not speak English as a first language, and it felt like everyone raised their hands. Matt also told us to “learn AI deeply”, well kind of 😉 He also almost spoiled where WCEU will be next year ?

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Closing remarks

But announcing the next year is something, that always rounds up the event. But before that, the organizing team was called on stage, followed by all volunteers. Then the moment everyone was finally there, the announcement of the next host city. Here is the video. SPOILER ALERT! Don’t read the tweet text 😉

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

So we are going to meet in Torino, Italy. I had the pleasure to visit Torino through their local WordCamp back in 2017. It’s a beautiful city with amazing food! So make sure to join us there!

The after party

As I had to wake up really early the next day to catch a ferry, I didn’t have the chance to be at the after party too long. I first met the other Inpsyders for a quick dinner and then joined the crowd for about 90 min. The venue reminded me a bit about Sofia. It was really loud insides, so many attendees just occupied the area before the main entrance and enjoyed the warm evening temperatures.

Some vacation

As this was may very first trip to Greece, and I also usually spend a full week for a WordCamp Europe, we booked for another week in Greece. We took a ferry to Ikaria on Sunday, and left for Kos Friday. Today we flew back to Berlin, full of memories.

A different WordCamp Europe

As this was my first WordCamp Europe not as an organizer, for quite a while, and it felt really different. Following the tradition, I did my Local Lead -> Global Lead -> Speaker cycle and can now retire. I won’t join the next organizing team, as there are some other things in my life that will be important in the time to the next WordCamp Europe. I might not even be able to attend myself. But if I can, I will either be courageous enough to apply for a regular talk, or at least apply as a volunteer, which I only did once back in 2016. So whatever the future will have for me, we see each other in Tornio … or wherever it will be in 2025 ?

20 years of WordPress – My personal journey

I usually write a blog post on 20 June each year to celebrate the birthday of this blog. But today I want to celebrate #WP20 with you, the 20th birthday of our beloved open source software.

No longer a teenager

At 20 years old, WordPress is a grown up piece of software. It started quite small, weighing only around 250KB in size. Now it’s around 100 times heavier. But is also matured in many other ways.

I don’t want to talk too much about the history of the CMS. If you are interested in how it changed over the years, not only in size, then I can highly recommend the page DisplayWP I just found this week.

WordPress and me

So instead, I want to focus a bit on my personal journey with WordPress. The first version I have used was (most probably) version 2.7.3 back in June 2009. I was working in my first job after university and a colleague wanted to use it to present events with it. So even in back then, I was not using it as a classical blog. But since Custom Post Types were only introduced in version 2.9, I was using posts for the events.

As my colleague was searching for a functionality that was not in Core, I wrote my frist plugin, published it, and started blogging myself.

The community

In 2010, I’ve attended my first WordCamp here in Berlin. That’s when I first met other people using it and some of the “stars”, I was looking up to and from whom I’ve learned so much.

After the WordCamp in Cologne, the first German meetups were started. The closest one to me was in Potsdam, which I joined for their second meetup in December 2011.

As there was no WordCamp planned for 2012 in Germany, the Potsdam meetup group organized two WP Camp in 2012 and 2013.

The year 2013 marked my first contact to the international community, attending the very first WordCamp Europe in Leiden.

After this event, it was clear to me, that I wanted to do more with WordPress, so two years later, I switched jobs and joined VCAT, the company co-founded by one of the members (now organizer) of the Potsdam meetup group. So you could say I got my second job through the WordPress community. End of 2015 the Berlin meetup group (which I had been organizing since 2014) had it’s first “official” WordCamp.

Fast-forward to 2017, I’ve organized another WordCamp Berlin and joined the WordPress Europe organizing team at the same time. I can tell you that organizing two events at the same time, one as a Lead Organizer, is not a great idea ?

One reason to join the WCEU organizing team was always to bring WordCamp Europe to Germany, which we did in 2019.

Then the pandemic hit the community and changed the work life for many of us. It also showed me, that working from home and/or remote is something I can get used to. So last October I’ve joined Inpsyde, the company mainly responsible for organizing the first WordCamps in Germany and also highly involved in kicking off the German community back in 2004, just one year after WordPress was first released.

In about two weeks, I will finally give my first talk at a WordCamp Europe, where I will also meet many old and new friends.

How WordPress changed my life

After moving from my hometown to Berlin, I didn’t have many friends. But through the community, and my jobs for WordPress agencies, I’ve found many nice people. Some of them have become my best friends, even outside the WordPress events.

Attending WordCamps also gave me the opportunity to visit new places. These are the (international) places/countries, I have visited for the first time, just because I attended a WordCamp:

  • Prague
  • Norrköpping
  • Bilbao
  • Zürich
  • Milano
  • Brighton
  • Torino
  • Zagreb
  • Las Palmas
  • Helsinki
  • Philadelphia
  • Nashville
  • Leiden
  • Sofia
  • Seville
  • Belgrade
  • Porto
  • Bangkok

So my around 10 countries and one new continent I have first visited attending a WordCamp. And every time I met some people I already knew, but more importantly met new people, some of which I have talked to just online for many years.

Thank you, WordPress and happy birthday!

Without WordPress, I don’t know how my life would have been. I am thankful for the opportunities I got, and humbled at the same time, to consider myself part of a large global community.

I hope we have another 20 years (or more) to come, and I can’t wait to see how WordPress will look like, when it’s in its 40s, just like me ?

As I usually end my blog’s birthday blog posts with a video, I want to share with you a message from Mike Little, one of the two co-founders of WordPress, and one of the nicest people from the community I’ve met:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Nested functions in PHP and why you should avoid them!

This week, I reviewed the code of a website. The website was using some code snippets. In one of those snippets, I’ve found a PHP nested function. The code didn’t work as expected. Not because of the nested function, but it gave me the idea for this blog post. I cannot share the original code, but I hope I can give you an example on how nested function work and why you probably shouldn’t use them.

What is a nested function?

A nested function in PHP is a function declared in the body of another function. We could call them “outer function” and “inner function” or “nested function”. Here is an example for such a nested function:

function multiplyAllByTwo( $array ) {
	function multiplyByTwo( $value ) {
		return $value * 2;

	return array_map( 'multiplyByTwo', $array );

We have a function to process an array and multiple all its values by 2. By using a function here, we could “potentially” do the same things for multiple arrays. Inside that function, we declare another helper function, that multiplies a given value by 2. This is the nested function. It is passed to an array_map() call, which will apply it to every entry of the $array. So when we throw in an array, we get back the array with all values multiplied by 2:

$inputArray = [ 1, 2, 3, 4, 5 ];
$resultArray = multiplyAllByTwo( $inputArray );
print_r( $resultArray );
	[0] => 2
    [1] => 4
    [2] => 6
    [3] => 8
    [4] => 10

That’s great, so what’s the issue? Well, let’s try to multiply the resulting array again:

$inputArray   = [ 1, 2, 3, 4, 5 ];
$resultArray  = multiplyAllByTwo( $inputArray );
$resultArray2 = multiplyAllByTwo( $resultArray );
print_r( $resultArray );
print_r( $resultArray2 );

What are we expecting when running these two calls? A second array with all values multiplied by 2 again, right? But what to we get instead?

PHP Fatal error:  Cannot redeclare multiplyByTwo() ...

When we call the function a second time, we get a fatal error, because the function cannot be declared with the same name again. So a nested function only work for an “out function”, that is only run ones. So what could we do instead?

Don’t use a nested function

The easiest way would be to move the nested function out of the other function and just declare it in the global namespace as well:

function multiplyAllByTwo( $array ) {
	return array_map( 'multiplyByTwo', $array );

function multiplyByTwo( $value ) {
	return $value * 2;

Now we can safely run the (previously) outer function twice:

$inputArray = [ 1, 2, 3, 4, 5 ];
$resultArray1 = multiplyAllByTwo( $inputArray );
$resultArray2 = multiplyAllByTwo( $resultArray1 );
print_r( $resultArray1 );
print_r( $resultArray2 );
	[0] => 2
    [1] => 4
    [2] => 6
    [3] => 8
    [4] => 10
	[0] => 4
    [1] => 8
    [2] => 12
    [3] => 16
    [4] => 20

Now we get the result we want. But we also pollute the global namespace with many functions. And we need to make sure, that we don’t use the same function name for these different helper function. As an alternative, you can also declare the function to a variable and use this instead of the function’s name string. But then you would again do this inside the outer function, as otherwise that variable would not be available, or you would need to make the variable available inside the function using the global keyword. Both not really nice solution, and that’s why I don’t even want to show code snippets for them ?

So when you don’t really need a globally declared function, how can you solve it then? There is another way to do this.

Use an anonymous function

An anonymous function is often used in combination with functions like array_map() and similar function. Our code would look like this:

function multiplyAllByTwo( $array ) {
	return array_map( function ( $value ) {
		return $value * 2;
	}, $array );

In this example, we declare the function at the moment we need it. This also eliminates the need to come up with a nice name, and we all know that naming things is one of the “two hard things in programming” ?

With PHP 7.4 and higher, you can even use a nice little arrow function that lets you write a single line function for this snippet:

function multiplyAllByTwo( $array ) {
	return array_map( fn( $value ) => $value * 2, $array );

Looks nice, right?

Conclusion: when to use which?

I’d recommend to never use a nested function! While in other programming languages it might be a common pattern, in PHP it can easily lead to fatal errors and testing/debugging issues.

When you possibly need the logic of the “inner function” for multiple “outer functions”, then declare the function with a name, either in the global namespace, or in a PHP class.

When you need that logic only for this specific “outer function”, or it is really very basic like in this example, you can use an anonymous function or even an arrow function.

Missing sidebar widget after migration – what happened?

I’m writing this blog post on my new server. The old one will be shut down in about two hours. As it had quite a few WordPress sites and one Matomo instance hosted, migrating everything was quite a task.

Migrating the sites I’ve followed my “5 minute migration process” and it went all smoothly as expected. Only when opening my blog after the DNS changes, the sidebar was missing the first two text widgets. What happened here?

Emojis ?

For quite a while, WordPress natively supports emojis. You can just use them in a blog post and WordPress will show them. In the past, they were replaced with an SVG sprite. But since modern operating systems and browsers support them natively, that’s not necessary anymore.

But why was using emojis and issue after the migration? On the old server, I’ve used MySQL 8 and on the new one MariaDB 10. When exporting the database, I’ve just run the wp db export command as always. But after the import, the UTF-8 multibyte characters were broken. Instead of an emoji, I only got some ?? signs.

This most probably broke the unserialize() function WordPress is using, and the whole widget was broken. This caused the first two text widgets not being shown, even though they were in the database.

Exporting the database with utf8mb4

After finding this issue and some research, I’ve found a WP-CLI issue about this effect. I was able to get a working export, adding a flag for the default charset:

$ wp db export --default-character-set=utf8mb4

Importing this new SQL dump fixed the issue, and the widgets appeared again. And I also got emojis in the content of my blog posts.

Conclusion: Always update WP-CLI!

So I successfully managed to repair the database, but still wanted to know why this (still) happened. The issue states, that this was fixed in the db-command, and release with version 2.5.0 of WP-CLI. Now on that server, I was still using version 2.4.0 of the WP-CLI which didn’t include the fix.

After updating to the current version (2.7.1), I didn’t need to add the flag anymore and the export had emojis and other utf8mb4 characters encoded correctly.

So before you do important maintenance tasks, better always update the WP-CLI to the latest version, so you don’t run into issue, you might not find right away. If I had recognized this issue only in some days/week, fixing/synching the database would have become really challenging.

Handling timezones – how not to do it!

Last week, I had quite an interesting issue to deal with. The site had a custom post type for webinars. When you are registered, you were supposed to see a “Join” button some minutes before the webinar starts. There was also a setting in the backend to set the number of minutes, the button should be shown before the event.

For some reason, it was not working. As I had identified another issue with time calculations on this plugin, I quickly set the time to 300 minutes before the event, since the webinar had already started 5 minutes ago. This fixed the issue for the moment. But why has it happened at all? Investigating the issue showed some “clever code”.

Investigating the data

The plugin was using data from an external API. This API returned multiple fields, which were saved into postmeta. These are some of the fields that were used:

| post_id | meta_key                      | meta_value                   |
| 1234567 | webinar_created_by            | api                          |
| 1234567 | event_id                      | 12345                        |
| ...     | ...                           | ...                          |
| 1234567 | to_date                       | Montag, 27. März 2023        |
| 1234567 | date                          | Montag, 27. März 2023        |
| 1234567 | date_en                       | 27 Mar 2023                  |
| 1234567 | to_date_en                    | 27 Mar 2023                  |
| 1234567 | start_time                    | 19:00:00                     |
| 1234567 | end_time                      | 20:45:00                     |
| 1234567 | time_zone                     | Mitteleuropäische Sommerzeit |
| ...     | ...                           | ...                          |

Can you recognize something here? Yes, this is localized data. The timezone “Mitteleuropäische Sommerzeit” is the German name for “Central European Summer Time” (CEST, GMT+2). So the time is two hours ahead of UTC. That’s why I’ve set a value greater than 120 minutes, which safely fixed it. But when the timezone is set correctly, why does it fail?

Converting timezone strings

In that plugin, there was a custom function converting timezone strings to UTC offsets. The function looked something like this:

function get_timezone_mapping( $name = '' ) {
	$mapping = [
		// ...
		'Central Time' => '-6',
		'Central Standard Time' => '-6',
		'Canada Central Standard Time' => '-6',
		// ...
		'Portugal Winter Time' => '+0',
		'India Standard Time' => '+05:30',
		// ...
		'Восточноевропейское время' => '+2',
		'Eastern European Summer Time (Athens)' => '+3',
		'Eastern European Summer Time' => '+3',
		// ...
		'北京时间' => '+8',
		'台北時間' => '+8',
		'Seoul Time' => '+9',
		'日本時間' => '+9',

	if ( ! empty( $name ) ) {
		if ( ! empty( $mapping[ $name ] ) ) {
			return $mapping[ $name ];
		} else {
			return false;

	return $mapping;

You would pass the strings of a timezone, and it would return the UTC offset. For some timezones, there were different variants, some even translated into other languages. Only our “Mitteleuropäische Sommerzeit” was not in that list, so it returned false in this case.

When things broke

Now, this function and its return value was used to create a Date object. It was then compared to the current time (of the server). The codes looked something like this (simplified):

// Data dynamically queried from the database.
$webinar_data = [
	'time_zone' => 'Mitteleuropäische Sommerzeit',
	'date_en' => '27 Mar 2023',
	'start_time' => '19:00:00',
// ...
$timezone = $webinar_data['timezone'];
$start_date = $webinar_data['date_en'];
$start_time = $webinar_data['start_time'];
// ...
$timezone_mapping = get_timezone_mapping( $timezone );
$date_timezone = ! empty( $timezone_mapping ) && ! is_array( $timezone_mapping ) ? new DateTimeZone( $timezone_mapping ) : null;

$current_datetime = new DateTime( 'now', $date_timezone );
$start_datetime = $start_date ? new DateTime( $start_date . ' ' . $start_time, $date_timezone ) : null;
// ...
$pre_buffer_minutes = empty($minutes) ? 15 : absint($minutes);
// ...
// Subtract the buffer from the webinar's starting date & time.
$start_datetime->sub( new DateInterval( "PT{$pre_buffer_minutes}M" ) );
$is_within_time = $current_datetime->getTimestamp() >= $start_datetime->getTimestamp();

So let’s break it down and see where the issue lies. The webinar’s timezone is passed to the function get_timezone_mapping(), but since the string “Mitteleuropäische Sommerzeit” cannot be mapped, this function returns false, which then results in using null for $date_timezone on line 13. This timezone is then used in both new Date() calls. But what happens here? Let’s take a look at the resulting Date object of the two calls and which time they represent, when being called at “19:00” (server time):

$current_datetime = (new DateTime('now', null))->format('Y-m-d H:i:s');
// 2023-03-27 17:00:00
$start_datetime = (new DateTime('27 Mar 2023 19:00:00', null))->format('Y-m-d H:i:s');
// 2023-03-27 19:00:00

Since the second Date object $start_datetime represents a valid “date string”, PHP will create an object with that exact time, ignoring the timezone. The first Date object $current_datetime however is using the now string and also no valid DateTimeZone object passed as a second parameter. If none is passed, PHP will always use UTC. So even if the server (or WordPress system) runs on Central European Summer Time, PHP will still use UTC. That results in the two-hour difference between the time we are expecting to get and the one we actually get. And finally, when the $is_within_time boolean gets created, it will be false, until $pre_buffer_minutes plus our two-hour difference if reached.

How to deal with timezones in a better way?

As you can see from the code above, handling timezones in such a way is a really bad idea. Especially, when you also have translated timezone strings. You can’t possibly maintain an array with all different variants. So what to use instead?

I would suggest to always use an “industry standard” string including the timezone, when you specify a time and date. In the table at the beginning of this blog post, you can see the date written in two ways: “Montag, 27. März 2023” and “27 Mar 2023”. The first one is translated, the second one is not. But by splitting up the date, time and timezones into different values, you have to combine them again (as seen at line 16), in order to create “date objects” in different programming languages. Why not use a format like this instead, having all the parts combined?

$webinar_start = 'Mon, 27 Mar 2023 19:00:00 +0200';

This string combines all information about the time, date and timezone in one string. This is what you would get, when you use the $date->format('r') output format. It follows the standards RFC 2822/RFC 5322. When someone consumes your API, they can then use this string, create a “date object” from it, and get the time, date, or whatever they might need from it.


Dealing times and dates is often not easy to do in programming. If you operate on different timezones as well, things become even harder. You can decide to serve parts of your time/date information as separate values, but please make sure to also provide a string like this. And if you decide not to do it, then please, don’t translate timezones, month names, or anything similar, as it can really cause (sometimes hard to debug) issues, as the one explained here.

As for this plugin, I have yet to find a solution. If the API doesn’t add such a string in the future, I probably have to extend the static array in that function with more and more (translated) timezone strings, but I would never be sure, if not one day, a new value would become available I don’t have, causing the issue all over again.

My CloudFest Hackathon 2023 review

Last weekend, I went to Europa-Park in Rust, to attend my second CloudFest Hackathon. This year’s event had 11 projects, and 7 of them were WordPress projects (some other were only slightly connected to WordPress). Last year, I joined the Pluginkollektiv team, working on the popular Antispam Bee plugin. This year, the team worked on Statify, a privacy-friendly statistics plugin.

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Small steps to a new and better plugin

For the Antispam Bee project last year, we rewrote the whole plugin with the goal to release a new major version. This new version is now in its alpha phase, so even a year after the event, not quite ready. For Statify, the team lead Florian Brinkmann identified some issues an GitHub he wanted to see the team working at. Some of them were “low-hanging fruits”, other quite large issues or features.

A supposedly easy ticket

We had some people in our team that were new to contributions, and we tried to find some easy issues to work on. One issue with the title “Show title instead of permalink” sounded quite easy. The issue was almost 5 years old and there was already some constructive discussion. There was even a PR with a single line change to target one part of the issue. So this issue should be easy to resolve, right? Unfortunately, it was not. The issue was not closed since 2018 for a reason. Talking about it in the group, we found many edge-cases about why it needs more planning to resolve all of them.

Working on a solution and introducing new possibilities

After discussing many different approaches and testing some of them in a proof of concept, it became clear, that the best solution would be to store the actual post title in the database, when the page view was tracked by Statify. As the custom table of Statify is quite simple, we had two possible solutions:

  1. Add another column to the table
  2. Add a statifymeta table to store the title

We have decided to take the second approach. While it seems too much for such a little feature, the introduction of this new table would enable Statify (and any extending plugin) to store more metadata for every entry without the need to extend the table every time. It also makes updates of Statify easier, as updating existing tables can always cause issues on some environments.

Onboarding new people

I have to admit, that I have not written too much code. I only contributed a minor UX improvement. Instead, I helped others with their contributions. One member of the team, who does not write code on a regular basis, made his first contribution and the PR was reviewed and merged. Another team member working on the post title issue was also new to many things. After working for two days on the issue, it was time to commit the new code. Only then he mentioned, that he has never used git before, so I also gave him a quick-start into git, and he was able to commit the changes to his fork. By that time we realized, that some other changes created some merge conflicts, so he also learned a bit how to handle them. Now we have to test the new code before it can get merged. As this issue and other we worked on are quite large, in summary, we will probably release a new major version of Statify containing all the new bug fixes and features from the hackathon.

An event to visit!

While I cannot give you details on all the other teams, I would recommend to check out the #CFHack2023 hashtag to get an idea of the event. For me, it was another amazing event! Not only the “hacking”, but also the social parts of it. There have been some official parties and also some fun site activities like a Mario Kart tournament happening on Sunday evening.

On Monday, just after the announcement of the winners of the different categories, the German WordPress community held a spontaneous “meta meetup”, in which organizers from many different cities shared experiences and ideas from their meetups. We plan to have such a meeting more regularly now:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

The CloudFest

In the afternoon, there was a “WordPress Day” kicking off the actual CloudFest event. While the main CloudFest event starting on Tuesday was more of an “industry event”, the WordPress Day had different topics focussing on the WordPress ecosystem. But not the typical sessions you would see on a WordCamp and some of them more of product presentations.

In the evening, there was the “Come2Gather in the Streets” event at the main entrance of Europa-Park, with food stands, drinks and some live music. I finally had the chance to talk to some people that were as busy as I was on the previous days or who just arrived for the main event.

Unfortunately, I had to leave Tuesday at 11 am and didn’t have much time to see any sessions. I briefly visited the “Cloud Fair” expo area and talked to some sponsors of the event. I also missed the chance to ride some of the rollercoaster that opened just for CloudFest attendees. But last year I did that quite a bit.


While I was a bit “disappointed” of myself in terms of active code contributions this year, I really enjoyed onboarding new people to contribute to open source. All of them were excited and motivated to get their first experiences, and they want to continue contributing in the future.

If nothing comes up, I will be there again next year. Maybe I can even come up with a project to submit. For the one idea I had for this year, there was just not enough time to prepare something. If you have never contributed to open source before, I can highly recommend finding a hackathon around our or a WordCamp with a Contributor Day. You learn new skills and find many like-minded people!

Using git for your server configuration files

I recently got myself a new web server. On this new server, I’m playing around with OpenLiteSpeed, which I have never used before. It comes with its own admin dashboard to manage the server. But it writes everything into configuration files.

Sometimes you also want or need to manipulate configurations files manually. If not for OpenLiteSpeed, then maybe the SQL or PHP configuration. But if you don’t really know what you do, you can easily screw things up.

Versioning your configuration files

This is why I have started to put my main configuration files under version control. This has several benefits:

  • You can try things and revert them, if things are get broken
  • You have a “backup” of your configuration
  • You can use the same/similar configuration files on all your servers and synchronize them
  • You can see changes made by other processes – like after an update or when using configuration tools

Only version the most important files and ignore sensitive information

On many Linux server systems, the configuration files are stored in the /etc folder. You may not think that you simply version that whole directory. But that’s not a good idea, as you might version files that contain sensitive information or that would even screw up your whole server, if you do something wrong. I once accidentally deleted the /etc/passwd file, which was not really good. ?

Ignoring nginx configuration files

So on my old server, I’ve versioned only the /etc/php and the /etc/nginx folders in two separate git repositories. For the nginx configurations, I used the following .gitignore file:

# /etc/nginx/.gitignore

The second line ignores the dhparam-4096.pem files being used for better Diffie-Hellman key. I also ignored the *-enabled folder, which are just symlinks to the *-available folders. You can version them, if you want to keep track of which sites and modules are enabled. The modules-available also didn’t have too much value in my opinion, so I left this one out as well.

Ignoring OpenLiteSpeed configuration files

On the new server, I’m still figuring out how configuration files are structured. The OpenLiteSpeed files are stored in the folder /usr/local/lsws and as of now, I use the following .gitignore file:

# /usr/local/lsws/.gitignore
# Ignore all files by default but decent into subfolders

# Allow just some files and subfolders

# Still ignore some files and folders in those subfolders

As the /usr/local/lsws folder also contains binaries and log files, I’ve first ignored all files and folder. I then added only those folders containing the configuration files I wanted to put under version control.

When I upgrade some packages, a lot of “backup configuration files” appeared, so I ignored all with those file extensions in the previously allowed folders.

I might update this ignore list in the future, but as of now, I have all my files I want to have under version control.

Push the repository to a remote

As mentioned in the benefits, you might also use this approach to have a “backup” of your configuration files. While git (or any other version control system) is not an alternative to a backup, it can be a bonus to have them stored on some external server. As those configuration files are often not meant to be public, I use private git repositories on as my remote.

This also has the benefit, that I clone the repositories to my laptop and edit the configuration files in PhpStorm, which is a lot easier than using vim on the server. And I can also easily compare changes and revert some commits.

Automatically version changes

I usually do the commits (and pushes) manually. But in case you have changes to those files made by other processes, and you want to version every change, you could set up a cron to commit all changes. This is an example of a cron, that would auto-commit and push all changes to the nginx configuration files every 30 minutes:

30 * * * * cd /etc/nginx && git add -A && git commit -m "auto commit on `date`" && git push

Even new and deleted files would be versioned. But having a cron like this could also potentially result in a lot of commits, if you don’t have your ignores set correctly, as some of the new files might have been a log file that is changed quite frequently. So if you use a cron, you should check the auto-commits from time to time.


I really love git! You can use it for so many different things. Versioning my configuration files has saved me a lot of time trying to figure out why things are suddenly broken or when I wanted to make changes to many configuration files at once (editing them locally in PhpStrom).

Do you use a similar approach and might want to share your setup here as well? Or do you use git for other things? Then please leave a comment.