<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>A Geek with Guns - Self-Hosting</title>
    <link rel="self" type="application/atom+xml" href="https://www.christopherburg.com/tags/self-hosting/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://www.christopherburg.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2025-12-30T15:30:00-06:00</updated>
    <id>https://www.christopherburg.com/tags/self-hosting/atom.xml</id>
    <entry xml:lang="en">
        <title>Setup IPv6 in WireGuard</title>
        <published>2025-12-30T15:30:00-06:00</published>
        <updated>2025-12-30T15:30:00-06:00</updated>
        
        <author>
          <name>
            Christopher Burg
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.christopherburg.com/blog/setup-ipv6-in-wireguard/"/>
        <id>https://www.christopherburg.com/blog/setup-ipv6-in-wireguard/</id>
        
        <content type="html" xml:base="https://www.christopherburg.com/blog/setup-ipv6-in-wireguard/">&lt;h1 id=&quot;introduction&quot;&gt;Introduction&lt;&#x2F;h1&gt;
&lt;p&gt;Over the holiday break I finished upgrading all of my self-hosted services to make them available via IPv6. The upgrade was straightforward for all of my services except one: my WireGuard &lt;abbr title=&quot;Virtual Private Network&quot;&gt;VPN&lt;&#x2F;abbr&gt; server. I wanted to provide my VPN clients with IPv6 connectivity without using &lt;abbr title=&quot;Network Address Translation&quot;&gt;NAT&lt;&#x2F;abbr&gt;. Unfortunately most of the guides online use NAT for IPv4 and IPv6. NAT is necessary for IPv4, but goes against the very design of IPv6. I wanted to provide each client with a globally routable IPv6 address. This ended up being simple once I figured it out.&lt;&#x2F;p&gt;
&lt;p&gt;I previous wrote a guide &lt;a href=&quot;&#x2F;archive&#x2F;getting-a-static-ip-address-with-a-cheap-vps-and-wireguard&#x2F;&quot;&gt;for setting up WireGuard to share a public static IPv4 address&lt;&#x2F;a&gt;. The formatting was ruined in the transition from my old WordPress blog to this statically generated one, but it explains how to setup a WireGuard VPN server for IPv4. This post will explain how to setup a WireGuard VPN server for IPv6.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;preamble&quot;&gt;Preamble&lt;&#x2F;h1&gt;
&lt;p&gt;First a major caveat. As of this writing, I&#x27;ve had this iteration of my VPN server running for two days. I&#x27;ve tested it on my home network, on my phone&#x27;s cellular network, and on an IPv4 only network through my travel router. IPv6 appears to work in all cases. I haven&#x27;t finished thorough testing though. There may be a number of corner cases that don&#x27;t work. I will update this guide as I find and fix them. Therefore, if you find something broken, check back and I might have discovered it and posted the solution already.&lt;&#x2F;p&gt;
&lt;p&gt;I also pieced my setup together by taking bits and pieces of several online guides. I pillaged from &lt;a href=&quot;https:&#x2F;&#x2F;mkaczanowski.com&#x2F;ndppd-ipv6-ndp-proxy&#x2F;&quot;&gt;this guide&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;blog.frehi.be&#x2F;2022&#x2F;06&#x2F;11&#x2F;setting-up-wireguard-vpn-with-ipv6&#x2F;&quot;&gt;this guide&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;blog.miyuru.lk&#x2F;setup-wireguard-with-global-ipv6&#x2F;&quot;&gt;this guide&lt;&#x2F;a&gt;, and &lt;a href=&quot;https:&#x2F;&#x2F;utcc.utoronto.ca&#x2F;~cks&#x2F;space&#x2F;blog&#x2F;linux&#x2F;ModernProxyIPv6AndARP&quot;&gt;this guide&lt;&#x2F;a&gt;. If there are unnecessary configuration steps in this guide, I likely took it from one of these guides and didn&#x27;t test my final setup without the configuration. Such mistakes are entirely my fault.&lt;&#x2F;p&gt;
&lt;p&gt;In order to follow this guide, you need a source of IPv6 addresses and specifically a separate subnet from your hosting network. My &lt;abbr title=&quot;Internet Service Provider&quot;&gt;ISP&lt;&#x2F;abbr&gt; provides me with a &#x2F;48 prefix. This gives me roughly a bajillion addresses and subnets.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conventions-used-in-this-guide&quot;&gt;Conventions Used in This Guide&lt;&#x2F;h1&gt;
&lt;p&gt;For the purposes of this guide, I&#x27;m going to use the following IP addresses (&lt;code&gt;2001:db8&lt;&#x2F;code&gt; is the &lt;a href=&quot;https:&#x2F;&#x2F;www.rfc-editor.org&#x2F;rfc&#x2F;rfc3849&quot;&gt;prefix reserved for examples and documentation&lt;&#x2F;a&gt; for those not aware):&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234::&#x2F;48&lt;&#x2F;code&gt;: The prefix provided by our ISP.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234:9999::&#x2F;64&lt;&#x2F;code&gt;: The subnet of our hosting network. The subnet &lt;code&gt;9999&lt;&#x2F;code&gt; was chosen for readability purposes.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234:ffff::&#x2F;64&lt;&#x2F;code&gt;: The subnet provided to our WireGuard VPN clients. Throughout this guide remember that the subnet with numbers is for our hosting network and the subnet with letters is for our VPN clients.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234:9999::1&lt;&#x2F;code&gt;: The IPv6 address used by clients to connect to our WireGuard VPN server.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234:ffff::1&lt;&#x2F;code&gt;: The IPv6 address of the WireGuard interface. This differs from the above address in that a client only uses it after it has connected to the WireGuard VPN server through the above address. Once connected, clients will use this address as their gateway.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234:ffff::1:1&lt;&#x2F;code&gt;: The IPv6 address provided to the first of our WireGuard VPN clients. This address will be incremented subsequently so our second client will have an IPv6 address of &lt;code&gt;2001:db8:1234:ffff::1:2&lt;&#x2F;code&gt;, our third &lt;code&gt;2001:db8:1234:ffff::1:3&lt;&#x2F;code&gt;, etc.&lt;&#x2F;p&gt;
&lt;p&gt;I will also use &lt;code&gt;en0&lt;&#x2F;code&gt; as the name of the network interface of our WireGuard VPN server, &lt;code&gt;51820&lt;&#x2F;code&gt; as the port used to connect to WireGuard on our server, and &lt;code&gt;wg-vpn.conf&lt;&#x2F;code&gt; as the name of our WireGuard configuration file.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-actual-guide&quot;&gt;The Actual Guide&lt;&#x2F;h1&gt;
&lt;p&gt;The first thing you will need to do is ensure that the router between your hosting network and ISP is setup to route the subnet for our VPN clients to our VPN server. How you do this will depend on both your ISP and your router. For my Ubiquiti Cloud Gateway Ultra, I created a static route that routes the entire &lt;code&gt;2001:db8:1234:ffff::&#x2F;64&lt;&#x2F;code&gt; subnet to &lt;code&gt;2001:db8:1234:9999::1&lt;&#x2F;code&gt;. You will also need to configure your router&#x27;s firewall to allow incoming WireGuard traffic to your VPN server.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m hosting my VPN server on Fedora Server. By default, IPv6 forwarding isn&#x27;t enabled on Fedora Server. I enabled it by adding &lt;code&gt;net.ipv6.conf.all.forwarding=1&lt;&#x2F;code&gt; to &lt;code&gt;&#x2F;etc&#x2F;sysctl.conf&lt;&#x2F;code&gt; and issued the &lt;code&gt;sysctl -p&lt;&#x2F;code&gt; command to load the changes. I also added several other lines. &lt;code&gt;&#x2F;etc&#x2F;sysctl.conf&lt;&#x2F;code&gt; on my VPN server contains the following contents:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;net.ipv4.ip_forward=1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;net.ipv6.conf.en0.proxy_ndp=1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;net.ipv6.conf.all.forwarding=1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;net.ipv6.conf.en0.accept_ra=2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first line enables IPv4 forwarding. It&#x27;s enabled by default on Fedora Server, but I added the line just to ensure it&#x27;s always enabled. The second line enables proxying &lt;abbr title=&quot;Neighbor Discovery Protocol&quot;&gt;NDP&lt;&#x2F;abbr&gt; packets, which is used by IPv6 to discover neighboring devices. The final line enables router advertisements while IPv6 forwarding is enabled. I added it to the file when I was trying to dole out IPv6 addresses through another method. I&#x27;m not sure if it&#x27;s needed for my final setup. This is one of those potentially unnecessary configuration steps I mentioned in the preamble for this guide.&lt;&#x2F;p&gt;
&lt;p&gt;The only port I opened on my VPN server&#x27;s firewall is &lt;code&gt;51820&lt;&#x2F;code&gt; for &lt;abbr title=&quot;User Datagram Protocol&quot;&gt;UDP&lt;&#x2F;abbr&gt; packets. WireGuard only uses UDP so you don&#x27;t need to open the port for &lt;abbr title=&quot;Transmission Control Protocol&quot;&gt;TCP&lt;&#x2F;abbr&gt;. I also enabled masquerading capabilities. Masquerading capabilities are only needed for NAT, which means it&#x27;s only necessary for IPv4. You don&#x27;t need to enable it if you&#x27;re only using IPv6 on your VPN server.&lt;&#x2F;p&gt;
&lt;p&gt;Everything else happens in the WireGuard configuration file located at &lt;code&gt;&#x2F;etc&#x2F;wireguard&#x2F;wg-vpn.conf&lt;&#x2F;code&gt;. The first section of the configuration file sets up the interface:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Interface]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PrivateKey = &amp;lt;Your Server&amp;#39;s Private Key&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Address = 2001:db8:1234:ffff::1&#x2F;64
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;SaveConfig = false
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ListenPort = 51820
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is all pretty basic. &lt;code&gt;PrivateKey&lt;&#x2F;code&gt; obvious contains your server&#x27;s private key that was generated with &lt;code&gt;wg genkey&lt;&#x2F;code&gt;. &lt;code&gt;Address&lt;&#x2F;code&gt; is the IPv6 address of the WireGuard interface. Clients will use this address as their gateway once they&#x27;ve connected to our VPN server. &lt;code&gt;SaveConfig&lt;&#x2F;code&gt; determines if the current configuration is saved when the WireGuard interface is shutdown. I generate my configuration files with Ansible so I don&#x27;t want any state maintained and disable this feature. &lt;code&gt;ListenPort&lt;&#x2F;code&gt; is the port through which clients will connect to the VPN server.&lt;&#x2F;p&gt;
&lt;p&gt;The next section is where the heavy lifting is done. &lt;code&gt;PostUp&lt;&#x2F;code&gt; commands run when the WireGuard server is being started. &lt;code&gt;PostDown&lt;&#x2F;code&gt; commands run when the WireGuard server is being stopped. In this case, the &lt;code&gt;PostDown&lt;&#x2F;code&gt; commands simply undo the &lt;code&gt;PostUp&lt;&#x2F;code&gt; commands. Also note that &lt;code&gt;%i&lt;&#x2F;code&gt; stands for the WireGuard interface name, which is &lt;code&gt;wg-vpn&lt;&#x2F;code&gt; since the configuration file is &lt;code&gt;wg-vpn.conf&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostUp = ip6tables -A FORWARD -i en0 -o %i -j ACCEPT;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostUp = ip6tables -A FORWARD -i %i -j ACCEPT;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostUp = ip -6 neighbor add proxy 2001:db8:1234:ffff::1:1 dev en0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostUp = ip -6 neighbor add proxy 2001:db8:1234:ffff::1:2 dev en0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostUp = ip -6 neighbor add proxy 2001:db8:1234:ffff::1:3 dev en0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostDown = ip6tables -D FORWARD -i en0 -o %i -j ACCEPT;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostDown = ip6tables -D FORWARD -i %i -j ACCEPT;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostDown = ip -6 neighbor del proxy 2001:db8:1234:ffff::1:1 dev en0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostDown = ip -6 neighbor del proxy 2001:db8:1234:ffff::1:2 dev en0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostDown = ip -6 neighbor del proxy 2001:db8:1234:ffff::1:3 dev en0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;PostUp = ip6tables -A FORWARD -i en0 -o %i -j ACCEPT;&lt;&#x2F;code&gt; and &lt;code&gt;PostUp = ip6tables -A FORWARD -i %i -j ACCEPT;&lt;&#x2F;code&gt; enable IPv6 forwarding between the server&#x27;s network interface (&lt;code&gt;en0&lt;&#x2F;code&gt; in this example) and the WireGuard interface (&lt;code&gt;wg-vpn&lt;&#x2F;code&gt; in this example). This allowed IPv6 traffic originating from our clients to be forwarded out of the VPN server&#x27;s network interface and return traffic routed from the VPN server&#x27;s network interface to the appropriate client.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;PostUp = ip -6 neighbor add proxy 2001:db8:1234:ffff::1:1 dev en0&lt;&#x2F;code&gt; and the following two lines setup NDP proxies for each client. You could probably replace all of these lines with &lt;code&gt;PostUp = ip -6 neighbor add proxy 2001:db8:1234:ffff::&#x2F;64 dev en0&lt;&#x2F;code&gt;. Again I generate my configuration file with an Ansible playbook so it&#x27;s just as easy for me to loop through my list of clients and add a line per client. As mention above, the &lt;code&gt;PostDown&lt;&#x2F;code&gt; lines simply undo the &lt;code&gt;PostUp&lt;&#x2F;code&gt; lines when the WireGuard interface is stopped.&lt;&#x2F;p&gt;
&lt;p&gt;The final part of the file contains configuration information for each peer:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Peer]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PublicKey = &amp;lt;The Client&amp;#39;s Public Key&amp;gt; 
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AllowedIPs = 2001:db8:1234:ffff::1:1&#x2F;128
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Peer]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PublicKey = &amp;lt;The Client&amp;#39;s Public Key&amp;gt; 
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AllowedIPs = 2001:db8:1234:ffff::1:2&#x2F;128
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Peer]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PublicKey = &amp;lt;The Client&amp;#39;s Public Key&amp;gt; 
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AllowedIPs = 2001:db8:1234:ffff::1:3&#x2F;128
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is straightforward. &lt;code&gt;PublicKey&lt;&#x2F;code&gt; contains the client&#x27;s public key and &lt;code&gt;AllowedIPs&lt;&#x2F;code&gt; contains the IPv6 address we&#x27;re assigning to the client. Running &lt;code&gt;systemctl start wg-quick@wg-vpn.service&lt;&#x2F;code&gt; will bring the WireGuard interface up so clients can start connecting.&lt;&#x2F;p&gt;
&lt;p&gt;The configuration file for each client is simple:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Interface]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Address = 2001:db8:1234:ffff::1:1&#x2F;64
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PrivateKey = &amp;lt;The Client&amp;#39;s Private Key&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Peer]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AllowedIPs = ::&#x2F;0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Endpoint = [2001:db8:1234:9999::1]:51820
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PublicKey = &amp;lt;Your Server&amp;#39;s Public Key&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;Address&lt;&#x2F;code&gt; is the globally routable IPv6 address our VPN server is assigning to the client. &lt;code&gt;PrivateKey&lt;&#x2F;code&gt; is the client&#x27;s private key. There&#x27;s similarly little to say about the &lt;code&gt;[Peer]&lt;&#x2F;code&gt; section, which contains connection information for the VPN server.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;AllowedIPs = ::&#x2F;0&lt;&#x2F;code&gt; tells the client to route all IPv6 traffic through the WireGuard interface. &lt;code&gt;Endpoint&lt;&#x2F;code&gt; contains the IPv6 address clients use to connect to the VPN server. Note the square brackets. Because IPv6 addresses use &#x27;:&#x27; as a separator and &#x27;:&#x27; is also used by convention to separate an IP address from a port number, the square brackets are used to disambiguate the &#x27;:&#x27; in the IPv6 address from the &#x27;:&#x27; differentiating the port number.&lt;&#x2F;p&gt;
&lt;p&gt;When the client connects to the VPN server, it will have the globally routable IPv6 address of &lt;code&gt;2001:db8:1234:ffff::1:1&lt;&#x2F;code&gt;. If you connect to a website that tests IPv6 connectivity such as &lt;a href=&quot;https:&#x2F;&#x2F;test-ipv6.run&#x2F;&quot;&gt;this IPv4 and IPv6 connectivity test&lt;&#x2F;a&gt;, it should show &lt;code&gt;2001:db8:1234:ffff::1:1&lt;&#x2F;code&gt; as your IPv6 address.&lt;&#x2F;p&gt;
&lt;p&gt;There you have it, a WireGuard VPN server that provides clients with globally routable IPv6 addresses.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Advice for Self Hosters</title>
        <published>2025-12-23T19:45:00-06:00</published>
        <updated>2025-12-23T19:45:00-06:00</updated>
        
        <author>
          <name>
            Christopher Burg
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.christopherburg.com/blog/advice-for-self-hosters/"/>
        <id>https://www.christopherburg.com/blog/advice-for-self-hosters/</id>
        
        <content type="html" xml:base="https://www.christopherburg.com/blog/advice-for-self-hosters/">&lt;p&gt;I just finished upgrading my final self hosted service to be available over IPv6. This was a staged upgrade handled over several months. All of the upgrades went smoothly. I attribute this success to years of stupidity. I&#x27;ve made a number of mistakes over the years. Some of them cost me a great deal of time and grief. But they all taught me valuable lessons. This post is a collection of some of those lessons. If You&#x27;re interested in self-hosting, I hope this advice proves valuable to you.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;1-automate-everything&quot;&gt;1. Automate everything.&lt;&#x2F;h2&gt;
&lt;p&gt;This is probably the most important lesson I&#x27;ve learned over the years. The reason my server upgrades went so well is because all of my builds are automated. This allows me to treat all of my servers like cattle. When the time is right, I slaughter them. The time is usually right when there&#x27;s a major Fedora release. This is because most of my services run on Fedora Server at the moment. When a new major version is released, I don&#x27;t do a dnf system-upgrade. Instead I nuke the virtual machine that hosts my service, create a new one, and install everything from scratch.&lt;&#x2F;p&gt;
&lt;p&gt;My builds weren&#x27;t always automated. I manually built all of my servers for many years. Because of how arduous building some of my servers was, I would sometimes let the host operating system get woefully out of date. Eventually they would get to a point where I had to rebuild them. Sometimes this was due to a hardware failure. Sometimes it was due to a security vulnerability in my stack. What I discovered is that I didn&#x27;t remember all of the ins and outs of building the server. The task would take much longer because I had to rediscover everything I forgot. That isn&#x27;t a problem now that everything is automated.&lt;&#x2F;p&gt;
&lt;p&gt;This advice doesn&#x27;t apply only to building servers. Your backups should be automated. If your backups aren&#x27;t automated, they won&#x27;t get done. TLS certificate renewals should also be automated, especially now that certificate lifetimes are becoming shorter and shorter. &lt;em&gt;Everything&lt;&#x2F;em&gt; should be automated.&lt;&#x2F;p&gt;
&lt;p&gt;You&#x27;ll hear a lot of advice about the best automation tools. Each automation tool has ups and downs. I settled on Ansible for automating my system builds. It&#x27;s a good tool in general but configurations are done in YAML and YAML sucks. NixOS is an option more people seem to be adopting. The upside is the entire system build is declared in a configuration file. The downside is you have to use NixOS (I don&#x27;t think NixOS is bad, I just want to note that you can&#x27;t use the NixOS method with other operating systems). Don&#x27;t let yourself succumb to decision paralysis. Look up a few automation tools, pick one that looks good, and learn it. Even shell scripts are better than nothing.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2-consider-maintenance&quot;&gt;2. Consider maintenance.&lt;&#x2F;h2&gt;
&lt;p&gt;Every self hosted service will require maintenance. How easy or hard the maintenance is will determine how often it gets done. I&#x27;ll use TLS certificate management as an example. TLS certificates have a lifetime. That lifetime is &lt;a href=&quot;https:&#x2F;&#x2F;www.digicert.com&#x2F;blog&#x2F;tls-certificate-lifetimes-will-officially-reduce-to-47-days&quot;&gt;becoming shorter and shorter&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;From today until March 15, 2026, the maximum lifetime for a TLS certificate is 398 days.&lt;&#x2F;li&gt;
&lt;li&gt;As of March 15, 2026, the maximum lifetime for a TLS certificate will be 200 days.&lt;&#x2F;li&gt;
&lt;li&gt;As of March 15, 2027, the maximum lifetime for a TLS certificate will be 100 days.&lt;&#x2F;li&gt;
&lt;li&gt;As of March 15, 2029, the maximum lifetime for a TLS certificate will be 47 days.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Certificate management used to be much more complicated than today. You had to create an account with a certificate authority. Once you had an account, you had to verify your identity, usually by providing a picture of your government ID. Then you could get a certificate for your domains that was good for one to two years. The maintenance wasn&#x27;t insignificant, but you only had to go through the process every one or two years. However, it was often easier to create your own certificate authorities if your self hosted services that were only available on your local network or were only used by devices you controlled.&lt;&#x2F;p&gt;
&lt;p&gt;Now the maintenance burden is changing. As certificate lifetimes shrink, it&#x27;ll become infeasible to manually manage your TLS certificates. If automating your certificate maintenance isn&#x27;t possible, which it might not be for a service only available on your local network, you can still provide a secure connection to it using something like WireGuard. WireGuard keys have no prescribed lifetime. Depending on your situation, it might be easier to make your self hosted service only available over a WireGuard connection so you can avoid the hassle of dealing with certificates.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;3-simple-is-usually-better&quot;&gt;3. Simple is usually better.&lt;&#x2F;h2&gt;
&lt;p&gt;The best illustration of this rule is &lt;a href=&quot;&#x2F;blog&#x2F;if-you-re-reading-this&#x2F;&quot;&gt;my post about Caddy&lt;&#x2F;a&gt;. I migrated my reverse proxy server (and since then all of my HTTP servers) from Nginx to Caddy. The reason for this migration was that Caddy is much simpler to configure than Nginx. What can easily require two dozen lines of configuration in Nginx can often be done in a single line in Caddy.&lt;&#x2F;p&gt;
&lt;p&gt;My migration from OpenVPN to WireGuard was done for similar reasons. OpenVPN is a very flexible protocol. Unfortunately that flexibility comes with significant complexity and that complexity hides a lot of footguns. WireGuard on the other hand is opinionated. There&#x27;s very little flexibility. That lack of flexibility means configuring a WireGuard interface is simple and there are no obvious footguns.&lt;&#x2F;p&gt;
&lt;p&gt;Another example is my self hosted Nextcloud instance. My original Nextcloud instance was built (using Ansible, of course) by setting up the database backend, installing all of the required packages, copying the Nextcloud PHP files to the web root, and bringing everything up with systemd services. Then the Nextcloud team released their &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nextcloud&#x2F;all-in-one&quot;&gt;All-in-One Docker container&lt;&#x2F;a&gt;. Installing the All-in-One Docker container is &lt;em&gt;much&lt;&#x2F;em&gt; easier than building everything from scratch. I use it now because it&#x27;s simple to install and maintain (it can do automatic updates).&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll point to this blog as my final example. For many years this was a WordPress blog. Last year I finally &lt;a href=&quot;&#x2F;blog&#x2F;a-fresh-start&#x2F;&quot;&gt;migrated it to a static website&lt;&#x2F;a&gt;. I can&#x27;t overstate how much simpler this blog is to maintain. I no longer need a database or PHP-FPM. I don&#x27;t have to worry about the seemingly endless security issues that WordPress suffers. Best of all is I don&#x27;t have to worry about a WordPress update &lt;a href=&quot;https:&#x2F;&#x2F;wordpress.org&#x2F;gutenberg&#x2F;&quot;&gt;enshitifying writing and editing posts&lt;&#x2F;a&gt; (Gutenberg was such a terrible experience that I had a WordPress plugin to bring back the old editor). Now I can write my posts with any text editor I please.&lt;&#x2F;p&gt;
&lt;p&gt;I won&#x27;t go so far as to say that the simpler option is always better. But preferring the simpler option is a sane default.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;4-practice-your-recovery-plan&quot;&gt;4. Practice your recovery plan.&lt;&#x2F;h2&gt;
&lt;p&gt;I noted that you should have automated backups in my first suggestion. But a backup isn&#x27;t useful if you can&#x27;t restore the data. The only thing harder than getting people to backup their data is getting them to test restoring it.&lt;&#x2F;p&gt;
&lt;p&gt;Since I automate (see how important my first suggestion is) building my systems and I rebuild them every time there&#x27;s a major Fedora release, I have an opportunity to test my recovery plan roughly every six months (Fedora releases a new major version about twice a year). Part of my automated builds involves pulling data from my hot backup system. I know my restores work because I test them frequently. Get in the habit of testing yours. It&#x27;s much better to know how to restore backups when you don&#x27;t need them than to find out you can&#x27;t restore them when you do need them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;5-have-fun&quot;&gt;5. Have fun.&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ll level with you. If playing with operating systems, building servers, and learning networking doesn&#x27;t sound fun to you, self-hosting isn&#x27;t for you. If you&#x27;re not having fun when things are working, you definitely won&#x27;t have fun when things aren&#x27;t working.&lt;&#x2F;p&gt;
&lt;p&gt;Self-hosting is part research, part engineering, and part detective work. The research is the easy part. You think of a service you need, find what options are ability that fulfill that need, read the documentation (if there is no documentation, don&#x27;t use it), and end up with an idea of how well the service will fill your need and how easy it will be to setup and maintain. Then comes building the service. This is where the engineering part comes in. If you listen to my advice, you&#x27;ll automated the building process. But even manually building a service will require engineering effort. Then eventually something will go wrong. This is where the detective part comes in. What went wrong? What are the likely causes? How can those causes be address? Is it DNS? It can&#x27;t be DNS. It was DNS!&lt;&#x2F;p&gt;
&lt;p&gt;Life is too for voluntary suffering. If self-hosting isn&#x27;t a fun hobby to you, go find a hobby you&#x27;ll enjoy instead.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>This Blog Is Now Available via IPv6</title>
        <published>2025-10-17T12:00:00+00:00</published>
        <updated>2025-10-17T12:00:00+00:00</updated>
        
        <author>
          <name>
            Christopher Burg
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.christopherburg.com/blog/this-blog-is-now-available-via-ipv6/"/>
        <id>https://www.christopherburg.com/blog/this-blog-is-now-available-via-ipv6/</id>
        
        <content type="html" xml:base="https://www.christopherburg.com/blog/this-blog-is-now-available-via-ipv6/">&lt;p&gt;Faster speeds are the only benefit I received from &lt;a href=&quot;&#x2F;blog&#x2F;upgrades&#x2F;&quot;&gt;my recent Internet upgrade&lt;&#x2F;a&gt;. One of my biggest gripes with having to use CenturyLink when I bought this house was the complete lack of IPv6 support. Brightspeed, not surprisingly considering their incompetency, didn&#x27;t add IPv6 support when they took over for CenturyLink. Lakeland Communications, however, has full IPv6 support, which means this blog is now available via IPv6.&lt;&#x2F;p&gt;
&lt;p&gt;I consider this a major milestone. It&#x27;s something I&#x27;ve wanted for many years now. I always envied people who had an IPv6 enabled Internet connection. I also hate network administrators who have an IPv6 enabled Internet connection and refuse to utilize it. Therefore, I&#x27;m going to use this occasion to rant about those network administrators and explain why their refusal is laziness at best and idiocy at worst.&lt;&#x2F;p&gt;
&lt;p&gt;Even though IPv6 has been formalized by the &lt;abbr title=&quot;Internet Engineering Task Force&quot;&gt;IETF&lt;&#x2F;abbr&gt; since 1998, there remains a lot of misconceptions about the protocol. The biggest misconception is that the only major difference between IPv6 and IPv4 is that the former has a significantly larger address space. This misconception leads to some stupid objections to implementing it. The first is that IPv6 addresses can&#x27;t be memorized like IPv4 addresses. Anybody who has worked with large networks knows that IPv4 address can&#x27;t typically be memorized for very long either. This is why &lt;abbr title=&quot;Domain Name System&quot;&gt;DNS&lt;&#x2F;abbr&gt; exists. You shouldn&#x27;t refer to your systems by their IP addresses regardless of the version you&#x27;re using. You should have a DNS server that provides memorable names to your systems.&lt;&#x2F;p&gt;
&lt;p&gt;Another objection to implementing IPv6 is that it&#x27;s complicated to setup. It&#x27;s actually much easier to setup an IPv6 network than an IPv4 network. My home network is a good example. IPv4 interfaces don&#x27;t generate their own addresses (technically they can, but those addresses aren&#x27;t useful in most cases) so you need to either statically assign an address to each device or use a &lt;abbr title=&quot;Dynamic Host Configuration Protocol&quot;&gt;DHCP&lt;&#x2F;abbr&gt; server. I use DHCP to assign addresses to my devices. I operate two Kea DHCP servers configured in high availability mode for redundancy. Meanwhile, IPv6 can utilize protocols like &lt;abbr title=&quot;Stateless Address Autoconfiguration&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;IPv6#Stateless_address_autoconfiguration_(SLAAC)&quot;&gt;SLAAC&lt;&#x2F;a&gt;&lt;&#x2F;abbr&gt; to automatically generate their own addresses. My ISP provides me an IPv6 prefix, which my router automatically receives and advertises. Every IPv6 client can then generate an IPv6 address for itself based on that prefix. The addresses they generate are also globally routable.&lt;&#x2F;p&gt;
&lt;p&gt;The last part is important. There are no more unclaimed IPv4 addresses, which makes them a scarce commodity. Not every ISP is willing to provide a static IPv4 address. Those that do typically charge a monthly fee per static IPv4 address. I rent my single static IPv4 address and have no interest in paying for more. I need to play tricks to provide all of my services via that single IPv4 address. When you connect to this blog via IPv4, several things happen. First my router uses port forwarding to connect your browser to my reverse proxy server. My reverse proxy server then uses &amp;lt;abbr=&quot;Server Name Indication&quot;&amp;gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Server_Name_Indication&quot;&gt;SNI&lt;&#x2F;a&gt;&lt;&#x2F;abbr&gt; to determine where to route the connection. The connection is then proxied to the server that actually hosts this blog. This tower of redirection is necessary to host multiple websites and other servers via a single IPv4 address.&lt;&#x2F;p&gt;
&lt;p&gt;There are effectively (obviously not literally) unlimited IPv6 addresses. My ISP provides me a &#x2F;48 prefix. This gives me more IPv6 address than I could ever use. I could dole out 10 IPv6 addresses to every network enabled device in my house and still not make a dent in the number of addresses available to me. And all of those addresses are globally routable. This means when you connect to this blog via IPv6, you&#x27;re connecting directly to the server hosting it.&lt;&#x2F;p&gt;
&lt;p&gt;This fact frightens a lot of network administrators because they have this false concept that their local network is trustworthy whereas the Internet isn&#x27;t. They like all of the redirection and games IPv4 requires because they believe it establishes are strong barrier between their trustworthy network and the untrustworthy Internet. Anybody who has read the news about network exploits knows this is false. Local networks aren&#x27;t trustworthy. Every system you setup should be setup as if it were connected directly to the Internet. This is how I design my network.&lt;&#x2F;p&gt;
&lt;p&gt;Even though my router has a firewall, every system I setup also has a firewall. Those firewalls are configured with the minimum number of open ports necessary. I also ensure SELinux is enabled on all of my systems (many network administrators disable SELinux rather than learn how to use it). I minimize the amount of unencrypted traffic on my local network. For example, my reverse proxy has a dedicated WireGuard connection to each of my servers is proxies. All traffic between it and the proxied servers goes through those WireGuard connections. The main sources of unencrypted traffic are my DHCP servers and that&#x27;s because DHCP can&#x27;t be secured. My networking equipment does use DHCP Guard to ensure only my DHCP servers can provide leases through. IPv6 eliminates the need for those WireGuard connections by eliminating the need for the reverse proxy. It also eliminates the need for DHCP since devices can configure their own IPv6 addresses.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve heard a few network administrators complain that providing websites via IPv4 and IPv6 complicates setting up TLS. This might have been the case when getting signed certificates was largely a manual task, but it&#x27;s no longer the case now that getting signed certificates is largely automated. I&#x27;ve automated the task of getting signed certificates ever since Let&#x27;s Encrypt&#x27;s signing certificate was added to every major browser. Originally I did this via certbot, but now that &lt;a href=&quot;&#x2F;blog&#x2F;if-you-re-reading-this&#x2F;&quot;&gt;I use Caddy&lt;&#x2F;a&gt;, my HTTPS servers do it automatically (a few of my servers like my e-mail server still use certbot). Certbot and Caddy both operate perfectly well via IPv6. In fact this blog is secured by separate TLS certificates depending on if you connect via IPv4 or IPv6. If you connect via IPv4, you use the certificate obtained by Caddy on my reverse proxy. If you connect via IPv6, you use the certificate obtained by Caddy on the server hosting my blog.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll end my rant here since this post is getting longer than I anticipated. In summary, IPv6 is better than IPv4 in every way. Every objection to implementing IPv6 is dumb. Network administrators who have the option to implement IPv6 but don&#x27;t are wrong.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Upgrades</title>
        <published>2025-10-15T12:00:00+00:00</published>
        <updated>2025-10-15T12:00:00+00:00</updated>
        
        <author>
          <name>
            Christopher Burg
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.christopherburg.com/blog/upgrades/"/>
        <id>https://www.christopherburg.com/blog/upgrades/</id>
        
        <content type="html" xml:base="https://www.christopherburg.com/blog/upgrades/">&lt;p&gt;I&#x27;ve been more silent as of late than usual. This is because I&#x27;ve been spending a lot of my free time upgrading my network infrastructure in anticipation of a major Internet upgrade. Most of this involved backend work that you readers won&#x27;t notice. Suffice to say that I made major edits to my Ansible playbooks and redid several parts of my home network.&lt;&#x2F;p&gt;
&lt;p&gt;A bit more than five years ago my wife and I moved to this house located in the rural Wisconsin. We loved everything about the property except for one thing: the only Internet option was DSL. I had DSL when I was in college and wasn&#x27;t looking forward to going back to it after having enjoyed cable Internet for a decade, but the property was good enough that I was willing to make the sacrifice. One upside is the DSL was decent at 20 Mbps down and 1.5 Mbps up. I was also able to get a static IP address so I could continue self-hosting my services.&lt;&#x2F;p&gt;
&lt;p&gt;Eventually Starlink became available in my area so I signed up. It was a significant upgrade. I regularly got 200 Mbps down and between 15 and 20 Mbps up. The two downsides were that my Starlink connection went offline during severe storms and I couldn&#x27;t get a static IP address. Therefore, I kept the DSL so I had a backup when the weather was bad and could continue hosting my services. My home network had two gateways and each client was assigned the appropriate gateway from my DHCP servers. My self-hosted services used the DSL gateway and everything else used the Starlink gateway unless there was severe weather. When there was severe weather, I ran an Ansible script that rebuilt my DHCP servers&#x27; configurations to assign every client the DSL gateway. Each client would start using the DSL connection when their DHCP lease expired and renewed (I use short leases for this reason). I admit it wasn&#x27;t the most elegant solution, but it was good enough for how rarely the Starlink connection went offline.&lt;&#x2F;p&gt;
&lt;p&gt;When I first bought this house, the DLS was provided by CenturyLink. CenturyLink are a shower of bastards. Whenever the DSL went offline, I had to suffer through a minimum of five phone transfers to get to a tech that could actually fix the issue. Eventually CenturyLink sold its DSL business to Brightspeed. Brightspeed somehow managed to be worse.&lt;&#x2F;p&gt;
&lt;p&gt;About a year ago a contractor buried fiber down my road. I expected to receive some notice that an ISP would be providing fiber service in my area but no such notice ever arrived. I searched high and low for the ISP that owned the fiber but found none. A few months ago Brightspeed announced that my static IP address would change. My history with Brightspeed told me that the changeover wouldn&#x27;t go well so I searched for the owner of the buried fiber once again. This time I found the ISP, which is Lakeland Communications. I gave them a call and they confirmed that they provided fiber to my area. The only downside to me was the cost of installing the fiber from the road to my house. My house is far from the road so the cost of connecting my house to the fiber wasn&#x27;t cheap (it was reasonable though considering the distance).&lt;&#x2F;p&gt;
&lt;p&gt;I was able to push off Brightspeed&#x27;s static IP address assignment, which turned out to be a blessing. To say the static IP address change went poorly would be an understatement. They managed to fuck it up completely. I had no Internet connectivity after the change. I spent a total of two hours on the phone with their technical support, all of whom are worthless, to no avail. Fortunately, Lakeland was scheduled to complete the fiber installation two days after that so I was only without my self-hosted services for about 48 hours.&lt;&#x2F;p&gt;
&lt;p&gt;Lakeland Communications proved to be competent and easygoing. Because I self-host services including e-mail, I expected to need to sign up for a business account (what I&#x27;ve always had to do with other ISPs), but was told that I could self-host from a residential connection without any issue. They were also more than happy to let me use my router instead of theirs, which made reconfiguration my network as easy as changing the static IP address in my UniFi Network Controller. The speeds are good enough that I have to upgrade my router since I&#x27;m running an old Ubiquiti Security Gateway 3P and my Wi-Fi access points.&lt;&#x2F;p&gt;
&lt;p&gt;Now when you access this site, it should download significantly faster. I can finally make full use of a number of my self-hosted services too. It&#x27;s nice to have these capabilities again after five years of DSL restricting me to making sparse use of my services when I&#x27;m not home.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>If You&#x27;re Reading This</title>
        <published>2025-08-30T12:00:00+00:00</published>
        <updated>2025-08-30T12:00:00+00:00</updated>
        
        <author>
          <name>
            Christopher Burg
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.christopherburg.com/blog/if-you-re-reading-this/"/>
        <id>https://www.christopherburg.com/blog/if-you-re-reading-this/</id>
        
        <content type="html" xml:base="https://www.christopherburg.com/blog/if-you-re-reading-this/">&lt;p&gt;If you you&#x27;re reading this, I&#x27;ve configured my new reverse proxy correctly. I&#x27;ve spent the last couple of days performing a major overhaul of my network infrastructure. This overhaul gave me the opportunity to rethink a few of my servers. The biggest rework was with my reverse proxy. For years I&#x27;ve been using Nginx to provide TLS connections for my various self-hosted services. Many of my Nginx configuration files were nightmares to read and edit because of the complexity of some of the services I self-host. It was finally bad enough that I started searching for alternatives. I eventually came across &lt;a href=&quot;https:&#x2F;&#x2F;caddyserver.com&#x2F;&quot;&gt;Caddy&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Caddy fucks. Consider the Nginx reverse proxy configuration for this blog, which is one of the simplest configurations on my reverse proxy server:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;map $http_upgrade $connection_upgrade {
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    default upgrade;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    &amp;#39;&amp;#39; close;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;server {
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    listen *:80;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    server_name www.christopherburg.com;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    include conf.d&#x2F;certbot.conf;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    location &#x2F; {
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        return 301 https:&#x2F;&#x2F;www.christopherburg.com$request_uri;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;server {
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    listen *:443 ssl http2;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    server_name www.christopherburg.com;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    autoindex off;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    access_log &amp;lt;path to the access logs&amp;gt;;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    ssl_certificate &amp;lt;path to the certificate&amp;gt;;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    ssl_certificate_key &amp;lt;path to the certificate key&amp;gt;;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    ssl_trusted_certificate &amp;lt;path to the trust chain&amp;gt;;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    # A bunch of TLS configurations to ensure old, weak ciphers aren&amp;#39;t used.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    include conf.d&#x2F;ssl.conf;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    location &#x2F; {
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_pass http:&#x2F;&#x2F;&amp;lt;actual server hosting site&amp;gt;;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        include conf.d&#x2F;headers.conf;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_set_header X-Forwarded-Port $server_port;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_set_header X-Forwarded-Scheme $scheme;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_set_header X-Forwarded-Proto $scheme;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_set_header X-Real-IP $remote_addr;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_set_header Host $host;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_request_buffering off;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_read_timeout 86400s;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        client_max_body_size 0;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        # Websocket
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_http_version 1.1;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_set_header Upgrade $http_upgrade;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        proxy_set_header Connection $connection_upgrade;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now look at the Caddy equivalent:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;www.christopherburg.com {
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    reverse_proxy &amp;lt;actual server hosting site&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Caddy handles most of the heavy lifting. It ensures TLS is enabled, automatically pulls certificates for your sites via Let&#x27;s Encrypt, automatically redirects HTTP to HTTPS, and has sane defaults for all the reverse proxy configurations. Writing the Ansible playbook to build my reverse proxy took me about an hour and that&#x27;s with zero Caddy experience beforehand.&lt;&#x2F;p&gt;
&lt;p&gt;With this change comes another. I finally shutdown the old Wordpress blog. It&#x27;s URL, blog.christopherburg.com, now redirects to here. Setting up this redirect in Caddy was as simple as:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;blog.christopherburg.com {
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    redir https:&#x2F;&#x2F;www.christopherburg.com&#x2F;blog&#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Caddy is working so well that I&#x27;m wondering when the other shoe will drop. When will a weird edge case rear its ugly head and bring me hours of frustration as I try desperately to fix it? I don&#x27;t know the answer to that. Until it does appear though, I&#x27;m very impressed with Caddy. If you&#x27;re self-hosting websites, I strongly encourage you to check it out.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Ansible Copy versus Synchronize</title>
        <published>2024-10-31T04:00:00+00:00</published>
        <updated>2024-10-31T04:00:00+00:00</updated>
        
        <author>
          <name>
            Christopher Burg
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.christopherburg.com/blog/ansible-copy-versus-synchronize/"/>
        <id>https://www.christopherburg.com/blog/ansible-copy-versus-synchronize/</id>
        
        <content type="html" xml:base="https://www.christopherburg.com/blog/ansible-copy-versus-synchronize/">&lt;p&gt;I run a lot of servers since I self-host almost all of the online services that I use. To simplify my life, I automate as much of the work as I can with &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ansible&#x2F;ansible&quot;&gt;Ansible&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;When I started migrating from my WordPress site to this one, I built an Ansible playbook to automate building this site&#x27;s server. One step in that playbook is copying the files output by Zola, which are located on my controller system, to the web server.&lt;&#x2F;p&gt;
&lt;p&gt;The first version of my playbook utilized Ansible&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;docs.ansible.com&#x2F;ansible&#x2F;latest&#x2F;collections&#x2F;ansible&#x2F;builtin&#x2F;copy_module.html&quot;&gt;copy module&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;YAML&quot; class=&quot;language-YAML z-code&quot;&gt;&lt;code class=&quot;language-YAML&quot; data-lang=&quot;YAML&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Copying website files to the server.&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ansible.builtin.copy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;src&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;{{ website_repository_path }}&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;dest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&#x2F;var&#x2F;www&#x2F;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;seuser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;system_u&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;serole&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;object_r&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;setype&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;httpd_sys_content_t&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;owner&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;nginx&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;group&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;nginx&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;u+rx-w,g+rx-w,o+rx-w&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;website_repository_path&lt;&#x2F;code&gt; is the path on the controller system to the website files. The server I&#x27;m using uses SELinux, hence the &lt;code&gt;seuser&lt;&#x2F;code&gt;, &lt;code&gt;serole&lt;&#x2F;code&gt;, and &lt;code&gt;setype&lt;&#x2F;code&gt; parameters. Since the site is static, I set the permissions so the files are read-only (the web server should never write to this directory so there&#x27;s no reason to give it permission to do so).&lt;&#x2F;p&gt;
&lt;p&gt;I started the Playbook and went to do other things. I returned almost three hours later expecting the new website to be up and running. Instead the playbook was still copying the website files from my controller system to the web server. I logged onto the web server and verified that files were being copied, albeit very slowly. Over three hours to copy a small website obviously isn&#x27;t sustainable so I started looking for another solution.&lt;&#x2F;p&gt;
&lt;p&gt;My search lead me to the Ansible &lt;a href=&quot;https:&#x2F;&#x2F;docs.ansible.com&#x2F;ansible&#x2F;latest&#x2F;collections&#x2F;ansible&#x2F;posix&#x2F;synchronize_module.html&quot;&gt;synchronize module&lt;&#x2F;a&gt;. The documentation told me everything I needed to know:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;synchronize&lt;&#x2F;code&gt; is a wrapper around rsync to make common tasks in your playbooks quick and easy.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I use rsync to copy files between computers all the time. I know it&#x27;s fast. So I made a naive change in my playbook.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;YAML&quot; class=&quot;language-YAML z-code&quot;&gt;&lt;code class=&quot;language-YAML&quot; data-lang=&quot;YAML&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Copying website files to the server.&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ansible.builtin.synchronize&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;src&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;{{ website_repository_path }}&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;dest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&#x2F;var&#x2F;www&#x2F;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;seuser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;system_u&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;serole&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;object_r&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;setype&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;httpd_sys_content_t&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;owner&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;nginx&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;group&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;nginx&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;u+rx-w,g+rx-w,o+rx-w&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;mode&lt;&#x2F;code&gt; parameter differs between the &lt;code&gt;copy&lt;&#x2F;code&gt; and &lt;code&gt;synchronize&lt;&#x2F;code&gt; module. Moreover, the synchronize module doesn&#x27;t support the SELinux parameters. So I made another change.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;YAML&quot; class=&quot;language-YAML z-code&quot;&gt;&lt;code class=&quot;language-YAML&quot; data-lang=&quot;YAML&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Copying website files to the server.&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ansible.builtin.synchronize&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;src&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;{{ website_repository_path }}&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;dest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&#x2F;var&#x2F;www&#x2F;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;push&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Setting the permissions for the website files in &#x2F;var&#x2F;www.&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ansible.builtin.file&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;path&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&#x2F;var&#x2F;www&#x2F;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;state&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;directory&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;recurse&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;true&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;seuser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;system_u&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;serole&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;object_r&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;setype&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;httpd_sys_content_t&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;owner&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;nginx&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;group&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;nginx&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;u+rx-w,g+rx-w,o+rx-w&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The playbook ran, but when it got to the synchronize task, it stopped with an error. The error indicated that the playbook required the sudo password for the target system. I found this quite strange since I set the entire playbook to run with elevated privileges on the target system. The playbook already knows the sudo password. This is a good time to remind everyone to completely read the documentation before using something. Had I done so, I&#x27;d have seen this:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Currently, synchronize is limited to elevating permissions via passwordless sudo. This is because rsync itself is connecting to the remote machine and rsync doesn’t give us a way to pass sudo credentials in.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;That&#x27;s easy enough to work around. Tell the &lt;code&gt;synchronize&lt;&#x2F;code&gt; task to run with regular user permissions, which is done by including &lt;code&gt;become: &quot;no&quot;&lt;&#x2F;code&gt; in the parameter list (assuming you set &lt;code&gt;become: &quot;yes&quot;&lt;&#x2F;code&gt; globally in the playbook like I did). However, I could no longer synchronize the files directly to &lt;code&gt;&#x2F;var&#x2F;www&#x2F;&lt;&#x2F;code&gt; because the Ansible user doesn&#x27;t have permission to write to that directory. Therefore, I had to change the workflow slightly.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Synchronize the files from my controller system to a directory on the web server to which the Ansible user has write permissions.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Copy the files from that location to &#x2F;var&#x2F;www&#x2F;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Clean up the originally copied files.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Set the correct permissions on the files in &#x2F;var&#x2F;www&#x2F;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Ansible is helpful in that there is an &lt;code&gt;ansible_user&lt;&#x2F;code&gt; variable so I could write the files to &lt;code&gt;&#x2F;home&#x2F;{{ ansible_user }}&#x2F;www&#x2F;&lt;&#x2F;code&gt;. The steps I described above ended up looking like this.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;YAML&quot; class=&quot;language-YAML z-code&quot;&gt;&lt;code class=&quot;language-YAML&quot; data-lang=&quot;YAML&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Copying website files to the server.&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;become&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;no&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ansible.builtin.synchronize&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;src&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;{{ website_repository_path }}&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;dest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&#x2F;home&#x2F;{{ ansible_user }}&#x2F;www&#x2F;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;push&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Moving the website files to &#x2F;var&#x2F;www.&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ansible.builtin.copy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;remote_src&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;true&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;src&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&#x2F;home&#x2F;{{ ansible_user }}&#x2F;www&#x2F;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;dest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&#x2F;var&#x2F;www&#x2F;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Removing files at temporary copy location.&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ansible.builtin.file&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;path&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&#x2F;home&#x2F;{{ ansible_user }}&#x2F;www&#x2F;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;state&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;absent&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Setting the permissions for the website files in &#x2F;var&#x2F;www.&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ansible.builtin.file&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;path&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&#x2F;var&#x2F;www&#x2F;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;state&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;directory&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;recurse&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;true&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;seuser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;system_u&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;serole&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;object_r&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;setype&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;httpd_sys_content_t&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;owner&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;nginx&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;group&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;nginx&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;u+rx-w,g+rx-w,o+rx-w&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When I ran the playbook with these changes, it took only a few seconds to copy the files from the controller system to the web server. The longest operation was setting the file permissions once the files were moved to &lt;code&gt;&#x2F;var&#x2F;www&#x2F;&lt;&#x2F;code&gt;, which took no more than 30 seconds. This is sustainable.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re using Ansible to copy a lot of files from one system to another, consider the &lt;code&gt;synchronize&lt;&#x2F;code&gt; module. It&#x27;s significantly faster.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
