<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David Foley</title>
    <description>The latest articles on DEV Community by David Foley (@dsofeir).</description>
    <link>https://dev.to/dsofeir</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F66872%2Ff1342567-d2fb-4077-aefb-bc495fa0e3ba.jpeg</url>
      <title>DEV Community: David Foley</title>
      <link>https://dev.to/dsofeir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dsofeir"/>
    <language>en</language>
    <item>
      <title>libvirt network forwarding inconsistencies</title>
      <dc:creator>David Foley</dc:creator>
      <pubDate>Tue, 17 Feb 2026 22:44:00 +0000</pubDate>
      <link>https://dev.to/dsofeir/libvirt-network-forwarding-inconsistencies-e62</link>
      <guid>https://dev.to/dsofeir/libvirt-network-forwarding-inconsistencies-e62</guid>
      <description>&lt;p&gt;In an INCUS container on a KVM guest, ping commands are successful when executed on the container, but not when the are executed from the host on the other end.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Change the libvirt virtual bridge forwarding mode to "open"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Consider a scenario where there is a group of physical servers connected to a core router. On one of the servers a KVM guest is running, which in turn is a host for some INCUS containers. Various virtual bridges, firewalls and subnets are implicated in providing connectivity to the INCUS containers. Everything is verified to be configured correctly from the virtualisation to the routing.&lt;/p&gt;

&lt;p&gt;A given INCUS guest can initiaite successful ICMP pings to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;KVM guests on the same physical host &lt;/li&gt;
&lt;li&gt;Physical hosts external to it's own. &lt;/li&gt;
&lt;li&gt;The core router&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, ICMP pings intiated on any device external to the container's own physical server do not succeed. In other words: ping command are successful when executed on the container, but not when the are executed from the host on the other end.&lt;/p&gt;

&lt;p&gt;This is an inconsistent result and completely unintuitive.&lt;/p&gt;

&lt;p&gt;The root cause can be attributed to the libvirt virtual network forwarding mode and a misunderstanding about what mode was appropriate for my environment. I was using libvirt's forward mode "route", where "guest network traffic [is] forwarded to the physical network via the host's IP routing stack, but without having NAT applied.". The documentation does not mention that libivrt firewall rules are added which interferes with the traffic described above.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;The solution is to use libvirt's "open" forwarding mode: "As with mode='route', guest network traffic will be forwarded to the physical network via the host's IP routing stack, but there will be no firewall rules added to either enable or prevent any of this traffic."&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Open the editor the XML definition for your virtual network bridge
&lt;/h3&gt;

&lt;p&gt;On your KVM host, run the following command to begin editing the XML definition for your virtual network bridge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo virsh net-edit kvm_virbr0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;virsh&lt;/code&gt; is the command to manipulate libvirt&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;net-edit&lt;/code&gt; is the sub-command to edit networks managed libvirt&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kvm_virbr0&lt;/code&gt; is the name of the virtual network bridge to which my KVM guests connect to. Adjust where appropriate and your network name differs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Change the forwarding mode
&lt;/h3&gt;

&lt;p&gt;Edit the XML definition so that the forward mode = "open" as in the this example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
&amp;lt;network&amp;gt;&lt;br&gt;
  &amp;lt;name&amp;gt;kvm_virbr0&amp;lt;/name&amp;gt;&lt;br&gt;
  &amp;lt;uuid&amp;gt;345a0dcc-4f89-4f7f-xcv3-160d1145837c&amp;lt;/uuid&amp;gt;&lt;br&gt;
  &amp;lt;forward mode='open'/&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Save the file and close the editor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Restart the virtual network bridge
&lt;/h3&gt;

&lt;p&gt;There is much advice on how to do this across the internet, the only reliable method I have found is to restart the computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://libvirt.org/formatnetwork.html#elementsConnect" rel="noopener noreferrer"&gt;Relevant libvirt documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Photo by &lt;a href="https://unsplash.com/@jamie452" rel="noopener noreferrer"&gt;Jaime Street&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/assorted-color-signage-lot-on-road-during-daytime-dQLgop4tnsc" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This article was first posted at &lt;a href="https://www.dfoley.ie/blog/libvirt-network-forwarding-inconsistencies" rel="noopener noreferrer"&gt;https://www.dfoley.ie/blog/libvirt-network-forwarding-inconsistencies&lt;/a&gt; #indieweb #posse&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>firewalld</category>
      <category>libvirt</category>
      <category>lxc</category>
    </item>
    <item>
      <title>Raspberry Pi Pico NOT flashing with arduino-cli</title>
      <dc:creator>David Foley</dc:creator>
      <pubDate>Tue, 12 Sep 2023 14:28:47 +0000</pubDate>
      <link>https://dev.to/dsofeir/raspberry-pi-pico-not-flashing-with-arduino-cli-5a8c</link>
      <guid>https://dev.to/dsofeir/raspberry-pi-pico-not-flashing-with-arduino-cli-5a8c</guid>
      <description>&lt;p&gt;While flashing a Raspberry Pi Pico (RP2040) from a Raspberry Pi 3 I got the error "No drive to deploy", there's a relatively simple fix if hard to find on the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Flashing the Device
&lt;/h2&gt;

&lt;p&gt;While using &lt;a href="https://github.com/earlephilhower/arduino-pico" rel="noopener noreferrer"&gt;Earle Philhower's RP2040 board package&lt;/a&gt; in the arduino-cli to flash a Raspberry Pi Pico (RP2040) from a Raspberry Pi 3 (Raspberry Pi OS Lite, buster) I got the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Converting to uf2, output size: 516096, start address: 0x2000
No drive to deploy.
An error occurred while uploading the sketch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The board was accessible via serial, the arduino-cli was correctly rebooting the RP2040 to where an external drive was appearing at /dev/sda.&lt;/p&gt;

&lt;p&gt;I could find nothing on the internet except this &lt;a href="https://github.com/earlephilhower/arduino-pico/issues/5" rel="noopener noreferrer"&gt;bug report&lt;/a&gt;, however there was no effective solution proposed there. Wondering whether it was the board package or Raspberry Pi OS Lite, I decided that trying the &lt;a href="https://github.com/arduino/ArduinoCore-mbed" rel="noopener noreferrer"&gt;official Arduino Core for mbed enabled devices&lt;/a&gt; might help.&lt;/p&gt;

&lt;p&gt;I encountered the same difficulty. Looking around on the web, I find a &lt;a href="https://forum.arduino.cc/t/arduino-ide-fails-to-upload/865066/7" rel="noopener noreferrer"&gt;Arduino forum thread&lt;/a&gt; which had the solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: post_install.sh
&lt;/h2&gt;

&lt;p&gt;After installing &lt;a href="https://github.com/arduino/ArduinoCore-mbed" rel="noopener noreferrer"&gt;official Arduino Core for mbed enabled devices&lt;/a&gt; before you attempt to use it the first time you must run the shell script &lt;code&gt;sudo ~/.arduino15/packages/arduino/hardware/mbed_rp2040/2.5.2/post_install.sh&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/earlephilhower/arduino-pico" rel="noopener noreferrer"&gt;GitHub: Raspberry Pi Pico Arduino core, for all RP2040 boards by Earle Philhower&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/arduino/ArduinoCore-mbed" rel="noopener noreferrer"&gt;GitHub: Arduino Core for mbed enabled devices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@dhehaivan?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;dhehaivan&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/flash?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was first posted at &lt;a href="https://www.dfoley.ie/blog/raspberry-pi-pico-not-flashing-with-arduino-cli" rel="noopener noreferrer"&gt;https://www.dfoley.ie/blog/raspberry-pi-pico-not-flashing-with-arduino-cli&lt;/a&gt; #indieweb #posse&lt;/em&gt;&lt;/p&gt;

</description>
      <category>electronics</category>
      <category>posse</category>
      <category>laterpost</category>
    </item>
    <item>
      <title>dnf packaging errors for MariaDB and borgbackup</title>
      <dc:creator>David Foley</dc:creator>
      <pubDate>Mon, 11 Sep 2023 11:50:47 +0000</pubDate>
      <link>https://dev.to/dsofeir/dnf-packaging-errors-for-mariadb-and-borgbackup-9pb</link>
      <guid>https://dev.to/dsofeir/dnf-packaging-errors-for-mariadb-and-borgbackup-9pb</guid>
      <description>&lt;p&gt;I recently encountered an error on a database server when using dnf (yum) on CentOS 8 Stream. Dependencies for MariaDB and borgbackup created a problem and there was an unusual fix which I hadn't previously been aware of.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;module_hotfixes=true&lt;/code&gt; in MariaDB repo definition&lt;/li&gt;
&lt;li&gt;Enabled powertools repository as recommended by Fedora EPEL team&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When I went to use the dnf package manager, I get the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[david@mariadb0 ~]$ sudo dnf update
Last metadata expiration check: 12:00:00 ago on Mon 01 Jan 1970 00:00:00 UTC.
Error: 
 Problem 1: cannot install the best update candidate for package borgbackup-1.1.16-1.el8.x86_64
  - nothing provides python3.6dist(packaging) needed by borgbackup-1.1.17-1.el8.x86_64
 Problem 2: package MariaDB-shared-10.5.9-1.el8.x86_64 requires MariaDB-common, but none of the providers can be installed
  - cannot install the best update candidate for package mariadb-connector-c-3.1.11-2.el8_3.x86_64
  - package MariaDB-common-10.5.7-1.el8.x86_64 is filtered out by modular filtering
  - package MariaDB-common-10.5.8-1.el8.x86_64 is filtered out by modular filtering
  - package MariaDB-common-10.5.9-1.el8.x86_64 is filtered out by modular filtering
 Problem 3: package MariaDB-shared-10.5.9-1.el8.x86_64 requires MariaDB-common, but none of the providers can be installed
  - cannot install the best update candidate for package mariadb-connector-c-config-3.1.11-2.el8_3.noarch
  - package MariaDB-common-10.5.7-1.el8.x86_64 is filtered out by modular filtering
  - package MariaDB-common-10.5.8-1.el8.x86_64 is filtered out by modular filtering
  - package MariaDB-common-10.5.9-1.el8.x86_64 is filtered out by modular filtering
(try to add '--skip-broken' to skip uninstallable packages or '--nobest' to use not only best candidate packages)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The host hasn't been touched recently, this error arose spontaneously.&lt;/p&gt;

&lt;p&gt;It turns out there are two issues here: a) Default package filtering is leading to MariaDB's dependencies not being resolved; b) The Fedora EPEL repository was not setup correctly, as a result borgbackup's dependencies cannot be resolved. Fortunately there are some easy fixes, however it was hard to track this down on the web, hence this blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing MariaDB
&lt;/h2&gt;

&lt;p&gt;This bug was previously identified by the MariaDB community and I found a suggested fix in the &lt;a href="https://jira.mariadb.org/browse/MDEV-25675" rel="noopener noreferrer"&gt;bug report&lt;/a&gt;. The steps are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Edit the MariaDB repository definition: &lt;code&gt;sudo vi /etc/yum.repos.d/MariaDB.repo&lt;/code&gt;. Add &lt;code&gt;module_hotfixes=true&lt;/code&gt; to the end of the file&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;sudo dnf update --best --allowerasing&lt;/code&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I have updated a &lt;a href="https://serverfault.com/questions/1063317/package-mariadb-shared-requires-mariadb-common-but-none-of-the-providers-can-be/1079444#1079444" rel="noopener noreferrer"&gt;stack exchange&lt;/a&gt; thread related to this with the advice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing borgbackup
&lt;/h2&gt;

&lt;p&gt;This error arose because I did not pay attention to the &lt;a href="https://docs.fedoraproject.org/en-US/epel/#how_can_i_use_these_extra_packages" rel="noopener noreferrer"&gt;advice for setting up the Fedora EPEL repository&lt;/a&gt;. Execute the following and then try dnf again:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;dnf install 'dnf-command(config-manager)'&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dnf config-manager --set-enabled powertools&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;This article was first posted at &lt;a href="https://www.dfoley.ie/blog/dnf-packaging-errors-for-mariadb-and-borgbackup" rel="noopener noreferrer"&gt;https://www.dfoley.ie/blog/dnf-packaging-errors-for-mariadb-and-borgbackup&lt;/a&gt; #indieweb #posse&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webmaster</category>
      <category>mariadb</category>
      <category>laterpost</category>
    </item>
    <item>
      <title>Activate partial LV after drive failure</title>
      <dc:creator>David Foley</dc:creator>
      <pubDate>Sun, 10 Sep 2023 14:11:29 +0000</pubDate>
      <link>https://dev.to/dsofeir/activate-partial-lv-after-drive-failure-428p</link>
      <guid>https://dev.to/dsofeir/activate-partial-lv-after-drive-failure-428p</guid>
      <description>&lt;p&gt;I recently noticed a drive in my home NAS was pre-fail according to S.M.A.R.T. diagnostics, when I replaced the drive I found it difficult to mount the remaining drives in the LVM VG to recover data.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use command &lt;code&gt;lvchange -ay --partial example_logical_volume&lt;/code&gt; to bring a LVM LV with failed disks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;The LVM volume that the drive was a member of had three drives. The majority of the data in the group was backed up redundantly to a local and cloud drive using &lt;a href="https://borgbackup.readthedocs.io" rel="noopener noreferrer"&gt;borg backup&lt;/a&gt;. There was about 900GB of which was not backed up because it was deemed to be unimportant, certainly not important enough to warrant the cost of backing it up to a cloud server, as well as locally, like the other data. However since the the failing drive was still online I tried to save as much of the 900GB as possible.&lt;/p&gt;

&lt;p&gt;I first copied off as much as I could to a spare drive, which was quite successful. During this process, the faulty drive failed completely. This wasn't a problem as I had saved the 900GB and had the backups. The backups had never actually been used, so I did a &lt;code&gt;borg check&lt;/code&gt; to ensure they were consistent. Despite that I was still a little nervous and I wanted to access the two good drives in the LVM volume to pull off the remaining data. I knew this was possible but had never done it before and found it very difficult to find information on the web. Which is why I am writing this small blog post.&lt;/p&gt;

&lt;p&gt;In the end it turned out to be quite simple, all that was required was the use of the &lt;code&gt;--partial&lt;/code&gt; flag when using &lt;code&gt;lvchange&lt;/code&gt;. So the command I executed was &lt;code&gt;lvchange -ay --partial example_logical_volume&lt;/code&gt;. This brought the LV back up and I was able to access the data on the two remaining disks. See the &lt;a href="https://www.mankier.com/8/lvchange" rel="noopener noreferrer"&gt;lvchange manpage&lt;/a&gt;) for more information&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was first posted at &lt;a href="https://www.dfoley.ie/blog/activate-partial-lv" rel="noopener noreferrer"&gt;https://www.dfoley.ie/blog/activate-partial-lv&lt;/a&gt; #indieweb #posse&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>laterpost</category>
    </item>
    <item>
      <title>Website not working in any browser on iOS</title>
      <dc:creator>David Foley</dc:creator>
      <pubDate>Sun, 26 Jul 2020 23:23:03 +0000</pubDate>
      <link>https://dev.to/dsofeir/website-not-working-in-any-browser-on-ios-2p4n</link>
      <guid>https://dev.to/dsofeir/website-not-working-in-any-browser-on-ios-2p4n</guid>
      <description>&lt;p&gt;This week I encountered the most bizzare support request: "Is it possible that our website would not load on iPhones?". Don't be silly, "that's not a thing" I told the customer ... It appears I was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every browser on iOS behaved the same and gave vague feedback&lt;/li&gt;
&lt;li&gt;Curl gave the same error, but provided far better feedback&lt;/li&gt;
&lt;li&gt;The error was caused by a web server behind a reverse proxy sending out an &lt;code&gt;Upgrade: h2&lt;/code&gt; header which is not permitted in the HTTP/2 protocol (&lt;a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.2" rel="noopener noreferrer"&gt;RFC7540&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;To fix the problem, remove &lt;code&gt;Protocols h2&lt;/code&gt; from the Apache config&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Error
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Every browser (Safari, Chrome &amp;amp; Firefox) on iOS (iPhone and iPad) completely failed when loading the website&lt;/strong&gt;. At the same time the site was accessible and functionting normaly on all browsers on Mac, Linux, Windows and Android. The error on iOS presented slightly differently depending on which browser was being used, some gave vague feedback, others gave none.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safari and Firefox (iOS)
&lt;/h3&gt;

&lt;p&gt;When the URL was input into the address bar the browser indicated it was loading the site, then quickly failed and closed the tab. There was no error feedback whatsoever.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chrome (iOS)
&lt;/h3&gt;

&lt;p&gt;When the URL was input into the address bar the browser indicates it is loading the site, then quickly fails and shows the following error page with the vague error description "ERR_FAILED":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnawj8rxncflvyqnj4y90.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnawj8rxncflvyqnj4y90.png" alt="Chrome Error" width="783" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Investigation
&lt;/h2&gt;

&lt;p&gt;The website in question was a WordPress site running on a LAMP stack in an LXC container, all requests to the site were being routed through an NGINX reverse proxy. The configutation was managed by Ansible and the same roles had been applied to a number of other hosts. These were also exhibiting the same error which meant I had the same error on over 20 websites and applications. There was a lot of diversity in the group of containers affected, so this quickly allowed me to focus on a small set of commonanlities amongst the sites that were being affected.&lt;/p&gt;

&lt;p&gt;It was very difficult to know where to start becuase there was such little feedback on the error, so I started looking at the raw HTTP conversations using Firefox, this yielded nothing. So I then went to the command line and ran curl. BINGO, I got the the following error: &lt;code&gt;curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)&lt;/code&gt;. When I appended -v, I got the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   Trying 116.202.000.000:443...
* TCP_NODELAY set
* Connected to site.example.com (116.202.000.000) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=site.example.com
*  start date: May 23 09:25:56 2020 GMT
*  expire date: Aug 21 09:25:56 2020 GMT
*  subjectAltName: host "site.example.com" matched cert's "site.example.com"
*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x55ea761971d0)
&amp;gt; GET / HTTP/2
&amp;gt; Host: site.example.com
&amp;gt; User-Agent: curl/7.65.3
&amp;gt; Accept: */*
&amp;gt; 
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
* http2 error: Invalid HTTP header field was received: frame type: 1, stream: 1, name: [upgrade], value: [h2]
* HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)
* stopped the pause stream!
* Connection #0 to host site.example.com left intact
curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So it turns out the error was caused by an invalid header being sent to the client. The server advertises that the connection can be upgraded to HTTP/2 despite the fact the connection has already been established using HTTP/2. The protocol as defined in &lt;a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.2" rel="noopener noreferrer"&gt;RFC7540&lt;/a&gt; does not permit this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;8.1.2.2.  Connection-Specific Header Fields&lt;/p&gt;

&lt;p&gt;HTTP/2 does not use the Connection header field to indicate&lt;br&gt;
   connection-specific header fields; in this protocol, connection-&lt;br&gt;
   specific metadata is conveyed by other means.  An endpoint MUST NOT&lt;br&gt;
   generate an HTTP/2 message containing connection-specific header&lt;br&gt;
   fields; any message containing connection-specific header fields MUST&lt;br&gt;
   be treated as malformed (Section 8.1.2.6).&lt;/p&gt;

&lt;p&gt;The only exception to this is the TE header field, which MAY be&lt;br&gt;
   present in an HTTP/2 request; when it is, it MUST NOT contain any&lt;br&gt;
   value other than "trailers".&lt;/p&gt;

&lt;p&gt;This means that an intermediary transforming an HTTP/1.x message to&lt;br&gt;
   HTTP/2 will need to remove any header fields nominated by the&lt;br&gt;
   Connection header field, along with the Connection header field&lt;br&gt;
   itself.  Such intermediaries SHOULD also remove other connection-&lt;br&gt;
   specific header fields, such as Keep-Alive, Proxy-Connection,&lt;br&gt;
   Transfer-Encoding, and Upgrade, even if they are not nominated by the&lt;br&gt;
   Connection header field.&lt;/p&gt;

&lt;p&gt;Note: HTTP/2 purposefully does not support upgrade to another&lt;br&gt;
protocol.  The handshake methods described in Section 3 are&lt;br&gt;
believed sufficient to negotiate the use of alternative protocols.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The root cause, "How did this invalid header get sent to the client?"
&lt;/h3&gt;

&lt;p&gt;In my setup an NGINX reverse proxy recevied requests from the public internet and then forwarded them to Apache instances running in LXC containers on the same host. While the connection between the clients browser and the NGINX reverse proxy was over HTTPS and used HTTP/2, the connection onwards to the Apache server used neither. So when an Ansible role, which assumed that TLS should be setup on all Apache servers, inserted &lt;code&gt;Protocols h2&lt;/code&gt; into the Apache config it created the scenario described above and fell fowl of RFC7540.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix
&lt;/h3&gt;

&lt;p&gt;Don't advertised HTTP/2 twice, remove &lt;code&gt;Protocols h2&lt;/code&gt; from the Apache config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further work
&lt;/h2&gt;

&lt;p&gt;Understand why the three web browsers from different vendors had exactly the same error on iOS and why the shared their approach to the protocal error with curl.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was first posted at &lt;a href="https://www.dfoley.ie/blog/website-not-working-all-browsers-ios" rel="noopener noreferrer"&gt;https://www.dfoley.ie/blog/website-not-working-all-browsers-ios&lt;/a&gt; #indieweb #posse&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>apache</category>
      <category>ios</category>
      <category>reverseproxy</category>
    </item>
  </channel>
</rss>
