<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joshua Rothe</title>
    <description>The latest articles on DEV Community by Joshua Rothe (@joshrothe).</description>
    <link>https://dev.to/joshrothe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3533053%2Fe3357e15-e2ed-4a59-b88f-59afb1f98329.jpg</url>
      <title>DEV Community: Joshua Rothe</title>
      <link>https://dev.to/joshrothe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joshrothe"/>
    <language>en</language>
    <item>
      <title>Self-Hosting a Vaultwarden Password Manager</title>
      <dc:creator>Joshua Rothe</dc:creator>
      <pubDate>Fri, 27 Mar 2026 19:29:16 +0000</pubDate>
      <link>https://dev.to/joshrothe/self-hosting-a-vaultwarden-password-manager-3eaf</link>
      <guid>https://dev.to/joshrothe/self-hosting-a-vaultwarden-password-manager-3eaf</guid>
      <description>&lt;p&gt;&lt;em&gt;Full text can also be viewed &lt;a href="https://joshrothe.us/blog/2026/setting-up-a-vaultwarden-password-manager/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Password vaults are a convenient and secure way to manage multiple passwords. As data breaches become more and more &lt;a href="https://www.statista.com/statistics/273550/data-breaches-recorded-in-the-united-states-by-number-of-breaches-and-records-exposed/" rel="noopener noreferrer"&gt;common&lt;/a&gt;, security guidance changes lead to an inevitable mishmash of credentials that are impossible to remember when not used daily. The logic behind constantly-evolving password guidelines is beyond the scope of this guide, but recent word from NIST on password vaults &lt;a href="https://pages.nist.gov/800-63-4/sp800-63b.html" rel="noopener noreferrer"&gt;recommends their use&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Verifiers SHALL allow the use of password managers and autofill functionality. Verifiers SHOULD permit claimants to use the “paste” function when entering a password to facilitate password manager use when password autofill APIs are unavailable. Password managers have been shown to increase the likelihood that subscribers will choose stronger passwords, particularly if the password managers include password generators&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This guide walks through setting up a lightweight, locally-hosted password manager that allows the user (or users) to keep their accesses secure without needing to perform superhuman feats of memorization. It is written assuming the reader has a fully configured WireGuard VPN server on their home network, with all local traffic handled by the VPN specifically. This is necessary for security, not just for Vaultwarden; it's generally much simpler and safer to stand up your devops infrastructure when you don't have to &lt;a href="https://securityscorecard.com/resources/learning-center/are-open-ports-putting-your-network-at-risk/" rel="noopener noreferrer"&gt;open ports to the entire internet&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;A password vault (or password manager) is a secure, encrypted digital storage system for username and password combinations. Remembering one password is much simpler than having to remember multiple. Why NIST took so long to come to this conclusion is out of scope for this guide, but feel free to &lt;a href="https://www.hivesystems.com/blog/nists-updated-password-security-guidance" rel="noopener noreferrer"&gt;enjoy others' discourse&lt;/a&gt; on the topic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvi5u3i21qngj6y1mkxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvi5u3i21qngj6y1mkxa.png" alt="xkcd 936: Password Strength" width="740" height="601"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Source: &lt;a href="https://xkcd.com/936/" rel="noopener noreferrer"&gt;xkcd 936&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are several password managers to choose from; Bitwarden is a strong contender in the space, and I highly recommend it for the average person or enterprise. It is open source, has &lt;a href="https://bitwarden.com/blog/security-through-transparency-eth-zurich-audits-bitwarden-cryptography/" rel="noopener noreferrer"&gt;weathered security audits&lt;/a&gt;, and you can even self-host it. With their cloud-hosted service, your data is encrypted locally before it's sent to their servers, so even Bitwarden themselves could not access your information.&lt;/p&gt;

&lt;p&gt;Some downsides exist, though; it's a resource hog when hosted locally, and resources matter when you keep stacking services on your home Debian device. The average user does not need the scalability and infrastructure that it provides. Additionally, some services such as autofilling passwords require a paid subscription, even when self-hosting.&lt;/p&gt;

&lt;p&gt;Thus, this guide settles on Vaultwarden for a self-hosted password vault. Vaultwarden is a lightweight re-write of Bitwarden in Rust; it uses much less CPU and RAM (~50MB to Bitwarden's ~1-2GB), and as a bonus it is easier to set up. The process is explained below; the length of this write-up should speak to its simplicity.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;WireGuard VPN, configured to handle local address traffic.&lt;/li&gt;
&lt;li&gt;Linux server (Debian, can be the same machine hosting the WireGuard VPN).&lt;/li&gt;
&lt;li&gt;Basic command line familiarity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Initial Setup
&lt;/h2&gt;

&lt;p&gt;On the server meant to host the Vaultwarden instance, you will need to install &lt;code&gt;podman&lt;/code&gt; and set up the necessary folder structure in your home directory. You will also need to set up a self-signing certificate (acceptable since we are on a VPN). Note that docker is also an option and most guides reference it; I prefer podman since &lt;a href="https://www.anantacloud.com/post/why-red-hat-podman-is-good-alternate-of-docker" rel="noopener noreferrer"&gt;it is more secure&lt;/a&gt; by not requiring root access and not having a single point of failure (&lt;code&gt;dockerd&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;First, find your server's IP address. You will need to replace &lt;code&gt;192.168.1.100&lt;/code&gt; below with your server's IP address. With the correct IP, run the following to execute the podman install, folder structure creation, and certificate generation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;podman podman-compose git &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/vaultwarden/&lt;span class="o"&gt;{&lt;/span&gt;data,config,ssl&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/vaultwarden
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ADMIN_TOKEN=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 .env
openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:4096 &lt;span class="nt"&gt;-keyout&lt;/span&gt; ssl/private.key &lt;span class="nt"&gt;-out&lt;/span&gt; ssl/certificate.crt &lt;span class="nt"&gt;-days&lt;/span&gt; 36500 &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/CN=192.168.1.100"&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ssl/private.key
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 ssl/certificate.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The certificate expires in 100 years (36,500 days). Expiration cannot be disabled completely, but if you have it set for a year your password manager may break and you could waste hours debugging the self-signed certificate you've since forgotten about.&lt;/p&gt;

&lt;p&gt;You may also need to open the HTTPS port on your firewall if you use UFW. Run &lt;code&gt;sudo ufw status&lt;/code&gt; and if it shows as active, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow from 192.168.1.0/24 to any port 8443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;Still within the &lt;code&gt;~/vaultwarden&lt;/code&gt; folder, create &lt;code&gt;docker-compose.yml&lt;/code&gt; by running the following (again, replace &lt;code&gt;192.168.1.100&lt;/code&gt; with the correct IP address):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; docker-compose.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
version: '3.8'
services:
  vaultwarden:
    image: docker.io/vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    env_file:
      - .env
    environment:
      WEBSOCKET_ENABLED: 'true'
      SIGNUPS_ALLOWED: 'true'
      DOMAIN: 'https://192.168.1.100:8443'
      ROCKET_TLS: '{certs="/ssl/certificate.crt",key="/ssl/private.key"}'
      LOG_LEVEL: 'warn'
      EXTENDED_LOGGING: 'true'
    volumes:
      - ./data:/data
      - ./ssl:/ssl:ro
    ports:
      - "8443:80"
      - "3012:3012"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the container and check status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
podman ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the &lt;code&gt;docker.io/vaultwarden/server:latest&lt;/code&gt; container running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Account Setup
&lt;/h2&gt;

&lt;p&gt;Access the admin console by typing &lt;code&gt;https://192.168.1.100:8443&lt;/code&gt; (replace the IP address) into a browser. You will be prompted to create the username and password, and then download the Bitwarden password manager addon. It works perfectly with Vaultwarden and autofills your passwords, so this is highly recommended.&lt;/p&gt;

&lt;p&gt;Next, configure it for your self-hosted server when logging in by selecting 'self-hosted' at login rather than 'bitwarden.com'. Use the admin console URL/port again, and log in using the login created earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Start/Restart of Server
&lt;/h2&gt;

&lt;p&gt;The last step here is to ensure Vaultwarden starts back up if the server reboots or has a power failure. This is also very simple to set up by running the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nb"&gt;enable &lt;/span&gt;podman.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;loginctl enable-linger &lt;span class="nv"&gt;$USER&lt;/span&gt;

podman generate systemd &lt;span class="nt"&gt;--new&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; vaultwarden &lt;span class="nt"&gt;--files&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.config/systemd/user
&lt;span class="nb"&gt;mv &lt;/span&gt;container-vaultwarden.service ~/.config/systemd/user/

podman-compose down
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; daemon-reload
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nb"&gt;enable &lt;/span&gt;container-vaultwarden.service
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; start container-vaultwarden.service

systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; status container-vaultwarden.service
podman ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code sets up auto-start configuration and generates a &lt;code&gt;systemd&lt;/code&gt; file for the Vaultwarden service, then brings the Vaultwarden container down to switch over from &lt;code&gt;podman-compose&lt;/code&gt; management to &lt;code&gt;systemd&lt;/code&gt; management. The last two lines check status; if these look good, you are done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You should now be able to use Vaultwarden in the browser of your choice. It should (when logged in) prompt you with the option to save passwords, and autopopulate them as needed. I hope this was helpful; if you notice any issues with this guide or process, please feel free to reach out or leave a comment.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>vaultwarden</category>
      <category>devops</category>
      <category>selfhosting</category>
    </item>
    <item>
      <title>Xilinx/AMD Vivado SoC FPGA Development and Debug Workflow</title>
      <dc:creator>Joshua Rothe</dc:creator>
      <pubDate>Sat, 18 Oct 2025 22:51:47 +0000</pubDate>
      <link>https://dev.to/joshrothe/xilinxamd-vivado-soc-fpga-development-and-debug-workflow-3mnp</link>
      <guid>https://dev.to/joshrothe/xilinxamd-vivado-soc-fpga-development-and-debug-workflow-3mnp</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover image source: AMD/Xilinx Zynq-7000 SoC Data Sheet: Overview DS190 (v1.11.1) July 2, 2018, Figure 1&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full text can also be viewed &lt;a href="https://joshrothe.us/blog/2025/xilinx-soc-fpga-development-and-debug-workflow-guide/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Verification of an FPGA design post-synthesis involves several steps, which can be incrementally worked through as the design matures. The following guide provides a decent outline to carry a design from post-simulation all the way to implementation alongside a SoC processor that can control and read the FPGA from software. I originally wrote this guide while working on my Master's degree, and revised it into a checklist as I found myself doing the same workflows. In situations where one missed step could require you to endlessly debug a phantom issue, it is helpful to have a repeatable process. While this guide is not all-encompassing, it functions as an excellent base framework for a junior FPGA designer to work from.&lt;/p&gt;




&lt;h1&gt;
  
  
  FPGA Overview (Written for Non-FPGA Developers)
&lt;/h1&gt;

&lt;p&gt;FPGAs are programmable hardware devices that are made up of a grid of repeating programmable logic blocks called "slices" and various other interlinked components, which have rapidly increased in complexity and performance in recent years. A modern Zynq Ultrascale+ MPSoC has two processors (APU and RPU), a GPU, various on-chip memory types (RAM, block RAM, distributed RAM), DMA controllers, serial transceivers, and dedicated I/O interfaces. On the boards they are mounted on, you will also often see additional memory types (DDR3/4), ADC and DACs, I/O peripherals, and more.&lt;/p&gt;

&lt;p&gt;Slices vary based on hardware but usually have the same general types of components. Referred to as Configurable Logic Blocks (CLBs) on the Ultrascale MPSoC, they are made up of Look-Up Tables (LUTs), flip-flops (FFs), and multiplexers. Some have additional components like adders or other logic gates thrown in.&lt;/p&gt;

&lt;p&gt;Newer FPGAs also have DSP slices, which are dedicated logic blocks for adders, multipliers, accumulators etc. and are instantiated differently to free up the CLBs (or, to reverse that, to free up the valuable multipliers and adders).&lt;/p&gt;

&lt;p&gt;When an FPGA is programmed, code such as SystemVerilog (syntactically similar to C) directs the synthesizer to connect or disconnect these slices, filling the lookup tables appropriately to achieve the coded performance on hardware. Understanding how the code synthesizes onto the FPGA is very important for the designer to know for several reasons.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Since you are programming hardware, some code is not synthesizable as it makes no sense to the compiler to put it on hardware. Examples include "wait" statements, or the "real" datatype. It is also important to be mindful that everything happens in parallel on hardware unless otherwise specified (e.g. a clock is added), so race conditions must be avoided and typical hardware design concerns such as set up time and hold time must be considered (this is referred to as timing closure in FPGA design, and is beyond the scope of this guide).&lt;/li&gt;
&lt;li&gt;You will want your code to utilize the resources on the FPGA as well as hardware resources on the board, and will need to design appropriately for your device. Instead of creating a large array of registers (which will not always properly synthesize into BRAM), you generally will want to instantiate a Xilinx IP for the BRAM component on the board in order to utilize it reliably. Clock signals, rather than being generated with regular FPGA fabric logic (e.g. counters or clock dividers), should be generated from dedicated clocking primitives (PLLs, MMCMs) to avoid poor timing and other issues.&lt;/li&gt;
&lt;li&gt;The processor(s), if used, need to be instantiated and connected to the design. How the design will be controlled from software must be considered, and those components must also be linked and created.&lt;/li&gt;
&lt;li&gt;How hardware components such as DDR3/4, DACs/ADCs, and I/O peripherals need to be controlled properly, which may include both firmware instantiation of controllers as well as proper register manipulation from software.&lt;/li&gt;
&lt;li&gt;Building off of the previous two items, the &lt;code&gt;component.xml&lt;/code&gt; file is hardware specific and must define the coded signals to the correct chip pins. Some proprietary FPGA synthesizers have a pre-configured setup, which also needs to be understood by the designer since that generally cannot be changed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For Xilinx FPGA design, the options are High Level Synthesis (HLS) coding in Vitis or Register Transfer Level (RTL) code in Vivado. RTL is typically preferred by electrical and FPGA engineers who need more control over hardware resources and timing, whereas HLS appeals more to software and embedded developers who can leverage C/C++ familiarity for rapid prototyping. This guide focuses on Vivado and RTL specifically, though Vitis is used in the debugging process as well.&lt;/p&gt;

&lt;p&gt;The following sections provide a step-by-step workflow for project creation through hardware debugging, assuming the reader has RTL source files ready for implementation.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building a Project
&lt;/h1&gt;

&lt;p&gt;Manually create a project in Xilinx Vivado with no sources, selecting only the applicable part type. Source files are then added to the project, and sim files are added separately (as they will be considered sim-only and not for synthesis). This project build can later be automated with &lt;code&gt;.tcl&lt;/code&gt; scripts, including the creation of a block diagram, once these source files are added and (if applicable) a block diagram is created. The actual construction of these source files is beyond the scope of this guide, but it assumes &lt;code&gt;.sv&lt;/code&gt;, &lt;code&gt;.v&lt;/code&gt;, or &lt;code&gt;.vhd&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;It is recommended to keep the source files linked, so that as they are modified the source files outside of the project will continuously update and can be re-added when a project is rebuilt. This may not always be desired behavior, however; especially if you have an automated &lt;code&gt;.tcl&lt;/code&gt; build workflow that manages source file versions independently.&lt;/p&gt;

&lt;p&gt;Typically, Vivado projects themselves are not saved, as rebuilding Vivado projects is a common way to fix many errors. The project files should be added to &lt;code&gt;.gitignore&lt;/code&gt; in most cases.&lt;/p&gt;




&lt;h1&gt;
  
  
  Timing Constraints and Synthesis/Implementation Analysis
&lt;/h1&gt;

&lt;p&gt;Before proceeding to hardware debugging, proper timing constraints need to be defined in the constraints &lt;code&gt;.xdc&lt;/code&gt; file. All clocks should be defined by &lt;code&gt;create_clock&lt;/code&gt; entries, and &lt;code&gt;set_clock_groups&lt;/code&gt; should define all clock domain crossings. Input or output delays relative to external devices should also be defined using &lt;code&gt;set_input_delay&lt;/code&gt; and &lt;code&gt;set_output_delay&lt;/code&gt;, respectively. An example is shown below, which can be added to the top-level project's constraints file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Primary clock constraints.&lt;/span&gt;
create_clock -period 10.000 -name sys_clk_100mhz &lt;span class="p"&gt;[&lt;/span&gt;get_ports sys_clk&lt;span class="p"&gt;]&lt;/span&gt;
create_clock -period 20.000 -name ext_clk_50mhz &lt;span class="p"&gt;[&lt;/span&gt;get_ports ext_clk&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Zynq processor clocks are auto-constrained when using Zynq IP, but any clocks generated from custom PLLs need manual definition.&lt;/span&gt;
create_generated_clock -name custom_clk_200mhz -source &lt;span class="p"&gt;[&lt;/span&gt;get_ports sys_clk&lt;span class="p"&gt;]&lt;/span&gt; -multiply_by 2 &lt;span class="p"&gt;[&lt;/span&gt;get_pins custom_pll_inst/clk_out&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Clock domain crossings - mark asynchronous clock groups which do not have a phase relationship.&lt;/span&gt;
set_clock_groups -asynchronous -group &lt;span class="p"&gt;[&lt;/span&gt;get_clocks sys_clk_100mhz&lt;span class="p"&gt;]&lt;/span&gt; -group &lt;span class="p"&gt;[&lt;/span&gt;get_clocks ext_clk_50mhz&lt;span class="p"&gt;]&lt;/span&gt;
set_clock_groups -asynchronous -group &lt;span class="p"&gt;[&lt;/span&gt;get_clocks sys_clk_100mhz&lt;span class="p"&gt;]&lt;/span&gt; -group &lt;span class="p"&gt;[&lt;/span&gt;get_clocks custom_clk_200mhz&lt;span class="p"&gt;]&lt;/span&gt;

# Input delays for external devices &lt;span class="p"&gt;(&lt;/span&gt;ADC example with 2ns setup, 1ns hold&lt;span class="p"&gt;)&lt;/span&gt;.
set_input_delay -clock &lt;span class="p"&gt;[&lt;/span&gt;get_clocks sys_clk_100mhz&lt;span class="p"&gt;]&lt;/span&gt; -max 2.0 &lt;span class="p"&gt;[&lt;/span&gt;get_ports &lt;span class="p"&gt;{&lt;/span&gt;adc_data&lt;span class="p"&gt;[&lt;/span&gt;*&lt;span class="p"&gt;]}]&lt;/span&gt;
set_input_delay -clock &lt;span class="p"&gt;[&lt;/span&gt;get_clocks sys_clk_100mhz&lt;span class="p"&gt;]&lt;/span&gt; -min 1.0 &lt;span class="p"&gt;[&lt;/span&gt;get_ports &lt;span class="p"&gt;{&lt;/span&gt;adc_data&lt;span class="p"&gt;[&lt;/span&gt;*&lt;span class="p"&gt;]}]&lt;/span&gt;

# Output delays for external devices &lt;span class="p"&gt;(&lt;/span&gt;DAC example with 3ns setup, 1ns hold&lt;span class="p"&gt;)&lt;/span&gt;.
set_output_delay -clock &lt;span class="p"&gt;[&lt;/span&gt;get_clocks sys_clk_100mhz&lt;span class="p"&gt;]&lt;/span&gt; -max 3.0 &lt;span class="p"&gt;[&lt;/span&gt;get_ports &lt;span class="p"&gt;{&lt;/span&gt;dac_data&lt;span class="p"&gt;[&lt;/span&gt;*&lt;span class="p"&gt;]}]&lt;/span&gt;
set_output_delay -clock &lt;span class="p"&gt;[&lt;/span&gt;get_clocks sys_clk_100mhz&lt;span class="p"&gt;]&lt;/span&gt; -min 1.0 &lt;span class="p"&gt;[&lt;/span&gt;get_ports &lt;span class="p"&gt;{&lt;/span&gt;dac_data&lt;span class="p"&gt;[&lt;/span&gt;*&lt;span class="p"&gt;]}]&lt;/span&gt;

# False paths for reset signals &lt;span class="p"&gt;(&lt;/span&gt;asynchronous resets don't need timing analysis&lt;span class="p"&gt;)&lt;/span&gt;. Also used if proper clock domain crossing circuits are being used - basically, avoids timing analysis.
set_false_path -from &lt;span class="p"&gt;[&lt;/span&gt;get_ports sys_reset_n&lt;span class="p"&gt;]&lt;/span&gt;

# Multicycle paths &lt;span class="p"&gt;(&lt;/span&gt;if there is logic that takes multiple clock cycles, e.g. pipelining or time-multiplexing expensive operations&lt;span class="p"&gt;)&lt;/span&gt;.
set_multicycle_path -setup 2 -from &lt;span class="p"&gt;[&lt;/span&gt;get_pins slow_logic_reg*/C&lt;span class="p"&gt;]&lt;/span&gt; -to &lt;span class="p"&gt;[&lt;/span&gt;get_pins output_reg*/D&lt;span class="p"&gt;]&lt;/span&gt;
set_multicycle_path -hold 1 -from &lt;span class="p"&gt;[&lt;/span&gt;get_pins slow_logic_reg*/C&lt;span class="p"&gt;]&lt;/span&gt; -to &lt;span class="p"&gt;[&lt;/span&gt;get_pins output_reg*/D&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above definitions will be required for Vivado to provide an accurate timing analysis.&lt;/p&gt;

&lt;p&gt;After implementation, review the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timing Summary: All timing constraints should be met. No negative slack. Failure to meet timing requires redesign, possibly adding latches between long datapaths (beyond the scope of this guide).&lt;/li&gt;
&lt;li&gt;Utilization Report: Aim for at most 80%, though many designers typically shoot for 60%-70%. You can run &lt;code&gt;report_qor_assessment&lt;/code&gt; to get exact thresholds (defined by Xilinx/AMD) for utilization targets; staying below these values gives the best QoR (quality of results) and easier timing closure. As utilization approaches 100%, the place and route tools have to work much harder to meet timing; this can also result in longer build times.&lt;/li&gt;
&lt;li&gt;Power Consumption: Verify it is within board capabilities. The system that you are integrating into likely has additional restrictions for power utilization that will need to be followed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Debugging Workflow over JTAG
&lt;/h1&gt;

&lt;p&gt;This instruction is intended for reference when revising an IP that exists (or will exist) within a larger block diagram. This allows for debugging using the XSCT console in Vitis as well as the Vivado ILA (Integrated Logic Analyzer). Overall goal is to verify functionality of an FPGA build by reading/manipulating registers and viewing the waveform outputs prior to adding the complexity of an OS (and related software code) to the design. This instruction assumes a Zynq processor or similar is on the chip, and there is/will be a software layer of some sort on that processor, which applies to most modern FPGAs. This instruction also assumes all of the simulation work has been completed, which is beyond the scope of this guide.&lt;/p&gt;




&lt;h2&gt;
  
  
  IP Creation
&lt;/h2&gt;

&lt;p&gt;In Vivado, design and simulate the RTL code for the IP. Once this is satisfactory, select &lt;strong&gt;Implementation &amp;gt; Run Implementation&lt;/strong&gt; from the Flow Navigator (left panel in Vivado GUI).&lt;/p&gt;

&lt;p&gt;Once implementation is complete, go to &lt;strong&gt;Tools &amp;gt; Package New IP&lt;/strong&gt;. Click through the prompts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select &lt;strong&gt;Packaging Options &amp;gt; Package your current project&lt;/strong&gt; on the second page.&lt;/li&gt;
&lt;li&gt;On the third page, choose an IP location that your overall block diagram project can pull from (here, &lt;code&gt;src/ip/&lt;/code&gt;), and create a sub-folder specifically for this IP, named appropriately. Select this folder as the location. &lt;em&gt;If rebuilding an existing IP, it is important to overwrite the same folder so that the block diagram automatically updates&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Once the Package IP tab comes up, take care of any issues. The &lt;strong&gt;Addressing and Memory&lt;/strong&gt; tab will allow for registers to be mapped out (select the proper size for the register in question if needed, so it is not taking up too much address space). After this, the File Groups tab allows you to merge changes from the wizard. Finally, go to the &lt;strong&gt;Review and Package&lt;/strong&gt; tab and select &lt;strong&gt;Re-package IP&lt;/strong&gt;. The temporary IP project will close.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Block Diagram Implementation
&lt;/h2&gt;

&lt;p&gt;Please note that block diagrams can also be implemented within a top-level RTL. The constraints will need to be set up accordingly for whichever the top level design is.&lt;/p&gt;

&lt;p&gt;For first-time implementation only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to the &lt;strong&gt;IP Catalog&lt;/strong&gt; in the Flow Navigator and right-click the background of the primary window. Select &lt;strong&gt;Add Repository&lt;/strong&gt; and add the location of the custom IP to the project. Then select &lt;strong&gt;Open Block Design&lt;/strong&gt; and add the IP to the block diagram. Connect as necessary in the &lt;strong&gt;Diagram&lt;/strong&gt; tab, and ensure the addressing is set up properly for any registers in the &lt;strong&gt;Address Editor&lt;/strong&gt; tab.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Debug Core Setup:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note: This section is written specifically for SoC processor integration.)&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Right click connections in the diagram that you would like debug probes to be added to, and select &lt;strong&gt;Debug&lt;/strong&gt;. This can be done to entire AXI/AXIS connections, or simple I/Os.&lt;/li&gt;
&lt;li&gt;Add a Zynq processor IP block to the design. Double-click the Zynq processor block to configure, and navigate to the &lt;strong&gt;PS-PL Configuration&lt;/strong&gt;. Within the &lt;strong&gt;PS-PL Cross Trigger&lt;/strong&gt; interface, enable &lt;strong&gt;Input Cross Trigger &amp;gt; Cross Trigger Input 0 &amp;gt; CPU0 DBG REQ&lt;/strong&gt; and &lt;strong&gt;Output Cross Trigger &amp;gt; Cross Trigger Output 0 &amp;gt; CPU0 DBG ACK&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Run Connection Automation&lt;/strong&gt; for the block diagram. It will connect the triggers to the Zynq processor, as well as the debug connections, to the ILA block that is automatically added. Double-click to configure the ILA block as needed. &lt;em&gt;This also needs to be done if any new debug cores are added&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Now go to &lt;strong&gt;Flow Navigator &amp;gt; Open Synthesized Design&lt;/strong&gt; and select &lt;strong&gt;Set Up Debug&lt;/strong&gt;. Add the corresponding nets that were added previously. *&lt;em&gt;This also needs to be done if any new debug cores were added&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Finally, &lt;strong&gt;Generate Bitstream&lt;/strong&gt;, and this should save the bitstream in the project's &lt;code&gt;projectname.runs/impl_1/&lt;/code&gt; folder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For subsequent implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vivado will detect the IP was upgraded and will prompt you to go to &lt;strong&gt;Report IP Status&lt;/strong&gt;, then &lt;strong&gt;Upgrade the IP&lt;/strong&gt;. Edits to the block diagram and ILA nets may be needed for some changes (reference the above italics).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Connecting to Hardware and Programming
&lt;/h2&gt;

&lt;p&gt;If the target is local, you can simply &lt;strong&gt;Connect to the Local Target&lt;/strong&gt;. This applies only when the FPGA board is connected directly to the computer running Vivado via a JTAG cable (note: newer Xilinx development boards have built-in JTAG functionality and this cable is now simply a USB-type cable).&lt;/p&gt;

&lt;p&gt;Most likely, the target is connected to a remote server. For Xilinx Vitis/Vivado debug purposes, it will need to be connected via the JTAG cable or interface.&lt;/p&gt;

&lt;p&gt;Follow these instructions to connect to a remote hardware server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, ssh into the server that the FPGA board is connected to via a USB-to-JTAG connection. Ensure the board's switches are configured to allow for JTAG programming (which varies by board). So the connection stays awake even without user input, run the following command to ssh into the server (with appropriate username and IP):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;ServerAliveInterval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30 username@SERVER.IP.GOES.HERE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Next, enter the following command to enable the Vivado hardware server:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hw_server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Back within Vivado, type the following in the GUI's XSCT console to connect to the remote hardware server (with the appropriate IP):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;connect_hw_server &lt;span class="nt"&gt;-url&lt;/span&gt; SERVER.IP.GOES.HERE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Once connected, you should be in the &lt;strong&gt;Hardware Manager&lt;/strong&gt;. Open it if not, then right click the board and select &lt;strong&gt;Open Target&lt;/strong&gt;. The board should be visible in &lt;strong&gt;Hardware&lt;/strong&gt; under the connected IP value.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Program Device&lt;/strong&gt;, select the exported bitstream file from earlier, and the exported &lt;code&gt;.ltx&lt;/code&gt; file from the debug build. It should be in the project folder under &lt;code&gt;projectname.runs/impl_1/&lt;/code&gt; and was generated with the &lt;strong&gt;Set Up Debug&lt;/strong&gt; wizard, during Implementation. Click &lt;strong&gt;Program&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If all debug probes look good, go to &lt;strong&gt;File &amp;gt; Export &amp;gt; Export Hardware&lt;/strong&gt;. Export a &lt;strong&gt;Fixed Platform&lt;/strong&gt;, and &lt;strong&gt;Include Bitstream&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Now, go to &lt;strong&gt;Tools &amp;gt; Launch Vitis IDE&lt;/strong&gt;. Leave Vivado and its Synthesis/Debug view open.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: Failing to properly close hw_server on the remote machine can cause issues with trying to start a new instance of hw_server. If you get the error &lt;code&gt;Cannot create listening port: Socket bind error. Address already in use&lt;/code&gt;, do the following to kill the process on the remote machine (assuming Linux), replacing 9085 here with the PID value returned with &lt;code&gt;pidof&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pidof hw_server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-9&lt;/span&gt; 9085 &lt;span class="c"&gt;# Replace 9085 with the correct PID value.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Configuring Vitis for Hardware/ILA Debug
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The cleanest way to do this is open a new workspace each time, since this avoids (and even fixes) numerous errors and snags that can occur. Typical method is to use the same folder but delete all contents prior to opening - you can also choose a new workspace each time if preferred. Once the workspace folder is selected, click &lt;strong&gt;Launch&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;File &amp;gt; New &amp;gt; New Application Project&lt;/strong&gt;. Go to the &lt;strong&gt;Create a new platform from hardware (XSA)&lt;/strong&gt; tab and load in the exported &lt;code&gt;.xsa&lt;/code&gt; hardware file. Load the Hello World template and click &lt;strong&gt;Finish&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the bottom left &lt;strong&gt;Explorer&lt;/strong&gt; panel, right click the &lt;code&gt;&amp;lt;app_project_name&amp;gt;_system&lt;/code&gt; entry (should be second from the top) and click &lt;strong&gt;Build Project&lt;/strong&gt;. This generates the necessary build files.&lt;/li&gt;
&lt;li&gt;Click the arrow next to the bug icon ("debug") and select &lt;strong&gt;Debug Configurations&lt;/strong&gt;. Double click &lt;strong&gt;Single Application Debug&lt;/strong&gt; to create a custom debug application.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Main&lt;/strong&gt; tab, choose the appropriate connection. You may need to create a &lt;strong&gt;New&lt;/strong&gt; one if the board is not local. The connection will save its variables even if you clear the workspace, but &lt;em&gt;this needs to be selected each time&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Target Setup&lt;/strong&gt; tab, make sure all of the reset, program and run checkboxes are enabled (not Skip Revision Check) and then also &lt;strong&gt;Enable Cross Triggering&lt;/strong&gt;. Modify this by clicking the &lt;strong&gt;...&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Cross Trigger Breakpoints&lt;/strong&gt;, create two. First, select &lt;strong&gt;CPU-0 &amp;gt; CPU0 DBGACK&lt;/strong&gt; in the left tab, and the entirety of &lt;strong&gt;FTM&lt;/strong&gt; on the right.&lt;/li&gt;
&lt;li&gt;For the second breakpoint, select &lt;strong&gt;CPU-0 &amp;gt; CPU0 debug request&lt;/strong&gt; on the right, and the entirety of &lt;strong&gt;FTM&lt;/strong&gt; on the left.&lt;/li&gt;
&lt;li&gt;Now click &lt;strong&gt;Debug&lt;/strong&gt; to close the &lt;strong&gt;Debug Configurations&lt;/strong&gt; window.&lt;/li&gt;
&lt;li&gt;At the arrow next to the bug icon again, click &lt;strong&gt;Debug As&lt;/strong&gt; and select the configuration that was just generated. The FPGA should program, restart, and run.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Debugging the Hardware with ILA and Register Read/Write
&lt;/h2&gt;

&lt;p&gt;Back in Vivado, click &lt;strong&gt;Window &amp;gt; Debug Probes&lt;/strong&gt;. The play button can be used to set up triggers that will configure it to read the waveform when the conditions are met (typically, you can set a valid signal low-to-high (R) to trigger for AXI signals). The double arrow symbol can be used to immediately show the waveform at the current moment regardless of triggers.&lt;/p&gt;

&lt;p&gt;In Vitis, the memory window on the left side can be used to view memory addresses. Using the following commands allows you to read and write from these registers, which in turn will correspond with the registers in your design (which is currently running on the hardware itself) if the address is mapped properly.&lt;/p&gt;

&lt;p&gt;Example read command (can use -force flag if the register is protected):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tcl"&gt;&lt;code&gt;mrd 0x50000000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example write command (writes a hex value of 1 to addr 5000_0000):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tcl"&gt;&lt;code&gt;mwr 0x50000000 0x00000001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While debugging with the ILA, pay attention to set up and hold time violations as well as clock domain crossing issues. Both are beyond the scope of this guide.&lt;/p&gt;




&lt;h2&gt;
  
  
  Debugging the Software with Vitis and the Integrated Logic Analyzer (ILA)
&lt;/h2&gt;

&lt;p&gt;Create a &lt;strong&gt;New Application Project&lt;/strong&gt; in Vitis as before, but set it as a blank C++ application and not Hello World. Add your software code in Vitis and build. The debugging tools are similar to the Eclipse IDE in that you can set breakpoints and view the machine code in Disassembly view.&lt;/p&gt;

&lt;p&gt;Assuming the hardware is connected via remote hw_server, you will need to open up a second terminal into that server. Set up minicom and connect to the board via UART (you will need a UART-to-USB connection in addition to the JTAG-to-USB connection) over 115200 baud, 8N1 and clear out modem settings A through H. This is typically &lt;code&gt;/dev/ttyUSB0&lt;/code&gt; or some increment thereof. Any print outputs triggered by the C++ code in Vitis will be displayed over this terminal. Continue to modify code, rebuild the Application Project, and run as needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Code Examples
&lt;/h2&gt;

&lt;p&gt;The following is listed here as a quick reference for creating register manipulation debug code.&lt;/p&gt;

&lt;h3&gt;
  
  
  AXI-4 Interface Protocol
&lt;/h3&gt;

&lt;p&gt;For Xilinx devices, the AXI-4 interface is a common data transfer method between FPGA modules. The AXI-4 Lite protocol (most common) will typically load a correct series of values on the master side, and set &lt;code&gt;valid&lt;/code&gt; to high when done. The slave side will set &lt;code&gt;ready&lt;/code&gt; high, and this handshake will allow a single burst of (typically 32-bit) data to transfer. The full AXI functionality adds burst and data protection (prot) and is outside the scope of the below snippet, but below will work on both AXI-4 and AXI-4 Lite.&lt;/p&gt;

&lt;p&gt;For a system controlled by AXI interfaces, the FPGA will map these interfaces to registers. For example, 0x8000_0000 and 0x8001_0000 may be mapped as the base addresses to two AXI registers, and the registers can both be written to directly from software by using commands such as what is shown below. The system automatically handles all of the handshakes required, so the programmer only has to worry about mapping the register and then reading or writing to it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Without an OS (Vitis)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;axi_write_phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u32&lt;/span&gt; &lt;span class="n"&gt;writeValue&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
                &lt;span class="n"&gt;Xil_Out32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AXI_SDR_ADDR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;writeValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Re-Configuring the Integrated Logic Analyzer
&lt;/h2&gt;

&lt;p&gt;When adding and removing debug cores, the design can end up getting buggy. Some notes on fixing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When adding nets, it is easy to mark them in netlist view (Right click &amp;gt; &lt;strong&gt;Mark for Debug&lt;/strong&gt;) and add them this way. When needing to debug a different set of nets, clear out the target constraints file of all synthesis-added parameters (these will be appended to the file and were for the debug cores). Then close the project and delete the &lt;code&gt;&amp;lt;project_name&amp;gt;.hw&lt;/code&gt; and &lt;code&gt;&amp;lt;project_name&amp;gt;.runs&lt;/code&gt; folders. This forces the project to do a clean synthesis, at which point you can add new debug cores. Then build as normal.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Debugging Workflow on OS
&lt;/h1&gt;

&lt;p&gt;Once the JTAG debug process has been proven functional, and the design is intended to run with an OS on the SoM, the next step is to build the software code on the OS and run it. To do this, the FPGA will need to be booted from an SD card containing both the firmware and the OS. Previously, it has been booting from JTAG.&lt;/p&gt;

&lt;p&gt;(Note: Creating and setting up a bootable OS is beyond the scope of this guide).&lt;/p&gt;

&lt;p&gt;Once the necessary files are loaded onto the SD card, ensure the boot select pins on the board are set to SD rather than JTAG, and power up the board. You can use minicom over USB to log in initially, and once SSH is set up, use SSH to enter the board and copy the necessary files over. QPSI-flash (Quad SPI) will need to be set up to allow persistence after boot. Software files should be built on the board itself, and then ran.&lt;/p&gt;

&lt;p&gt;Code for writing to registers on Linux is shown below for reference. From this point on, a designer will likely be manipulating registers from software and possibly setting up logging functionality to run on the processor.&lt;/p&gt;

&lt;h4&gt;
  
  
  With an OS (Running C/C++ Code on a Linux OS on the Zynq Processor)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;axi_write_phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;writeValue&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
                &lt;span class="cm"&gt;/* Map the memory */&lt;/span&gt;
                &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/dev/mem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;O_RDWR&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;O_SYNC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Need to close this when finished to avoid memory issues.&lt;/span&gt;
                &lt;span class="cm"&gt;/* Map the SDR regs */&lt;/span&gt;
                &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mm_sdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PROT_READ&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;PROT_WRITE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MAP_SHARED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AXI_SDR_ADDR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sdr_regs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;mm_sdr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sdr_regs&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writeValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: For the above, you will eventually need something to close the file descriptor, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;munmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mm_sdr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;That summarizes the general implementation and debug workflow from post-synthesis to on-hardware implementation. Please feel free to reach out if you find any mistakes or inaccuracies in this guide.&lt;/p&gt;




&lt;h1&gt;
  
  
  Acronyms
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;APU: Application Processing Unit&lt;/li&gt;
&lt;li&gt;RPU: Real-Time Processing Unit&lt;/li&gt;
&lt;li&gt;RTL: Register Transfer Level&lt;/li&gt;
&lt;li&gt;HLS: High Level Synthesis&lt;/li&gt;
&lt;li&gt;PLL: Phase-Locked Loop&lt;/li&gt;
&lt;li&gt;MMCM: Mixed-Mode Clock Manager&lt;/li&gt;
&lt;li&gt;RAM: Random Access Memory&lt;/li&gt;
&lt;li&gt;DMA: Direct Memory Access&lt;/li&gt;
&lt;li&gt;ADC: Analog to Digital Converter&lt;/li&gt;
&lt;li&gt;DAC: Digital to Analog Converter&lt;/li&gt;
&lt;li&gt;FPGA: Field Programmable Gate Array&lt;/li&gt;
&lt;li&gt;MPSoC: Multi Processor System on Chip&lt;/li&gt;
&lt;li&gt;DDR: Double Data Rate&lt;/li&gt;
&lt;li&gt;SoM: System On Module&lt;/li&gt;
&lt;li&gt;QoR: Quality of Results&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fpga</category>
      <category>xilinx</category>
      <category>amd</category>
      <category>vivado</category>
    </item>
    <item>
      <title>Setting Up a WireGuard VPN Client on Linux</title>
      <dc:creator>Joshua Rothe</dc:creator>
      <pubDate>Sun, 28 Sep 2025 01:35:12 +0000</pubDate>
      <link>https://dev.to/joshrothe/setting-up-a-wireguard-vpn-client-on-linux-3aan</link>
      <guid>https://dev.to/joshrothe/setting-up-a-wireguard-vpn-client-on-linux-3aan</guid>
      <description>&lt;p&gt;&lt;em&gt;Full text can also be viewed &lt;a href="https://joshrothe.us/blog/2025/setting-up-a-wireguard-vpn-client-on-linux/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Setting up a WireGuard VPN for privacy and security involves setting up both server and client side systems. This guide explains how to set up a client side Linux system - with or without &lt;a href="https://pi-hole.net/" rel="noopener noreferrer"&gt;Pi-hole DNS filtering&lt;/a&gt; on the home network - and then configure the system so that WireGuard settings will switch depending on if the client system is on the home network or not. This is necessary because the WireGuard client will break your network connection if you are on your home network, and there is no need to manually switch your VPN client on and off when automation exists.&lt;/p&gt;

&lt;p&gt;This guide assumes a &lt;a href="https://www.wireguard.com/quickstart/" rel="noopener noreferrer"&gt;WireGuard VPN server&lt;/a&gt; is set up, and port forwarding is configured on the home router (UDP, port 51820 should be forwarding to your WireGuard server). This guide is also written for Linux Mint - while this should also work for most Debian systems, you may need to modify some file paths depending on your distro.&lt;/p&gt;

&lt;p&gt;Note: This post was updated Oct 18 2025 to fix two critical issues with the original approach: the WireGuard client would break upon switching network types while the client was shut down, and it did not check if the WireGuard server itself was reachable when on an external network prior to enabling. This improved method fixes these issues and keeps the WireGuard client disabled until the following is verified:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client is not on the home network.&lt;/li&gt;
&lt;li&gt;The WireGuard server is reachable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The old method is kept in the Appendix for historical purposes, but is depreciated.&lt;/p&gt;




&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;VPNs are a great tool for security and privacy, with key benefits being: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy and Security:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Location Privacy&lt;/strong&gt;: All traffic appears to be from your home IP address. Your actual location is obscured to anyone tracking your browsing activity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Protection&lt;/strong&gt;: On public wireless networks, your VPN tunnel ensures packet sniffers and other malicious actors can't see your activity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audited:&lt;/strong&gt; WireGuard is open source and is &lt;a href="https://courses.csail.mit.edu/6.857/2018/project/He-Xu-Xu-WireGuard.pdf" rel="noopener noreferrer"&gt;audited regularly&lt;/a&gt; by both organizations and individuals. All of its code is viewable by any developer curious enough to know how it functions.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ad Blocking:&lt;/strong&gt; If you run a &lt;a href="https://pi-hole.net/" rel="noopener noreferrer"&gt;Pi-Hole&lt;/a&gt; system at home for ad blocking, you can use that same tool while off your home network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Home Network Access:&lt;/strong&gt; This secure tunnel to your home network means you can access devices on your home network such as cameras, NAS systems, and IoT devices without needing to expose them to the internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed:&lt;/strong&gt; &lt;a href="https://www.wireguard.com/performance/" rel="noopener noreferrer"&gt;WireGuard is fast&lt;/a&gt;, faster than most other comparable VPN services.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low Overhead:&lt;/strong&gt; Works on virtually all modern hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographically Secure:&lt;/strong&gt; Uses proven, modern cryptographic primitives detailed on the &lt;a href="https://www.wireguard.com/" rel="noopener noreferrer"&gt;WireGuard homepage&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free:&lt;/strong&gt; Self explanatory. No risk of subscription fees in the future, either. Open source comes with benefits!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you do not have a WireGuard VPN server set up and find this interesting, I've worked through the &lt;a href="https://www.wireguard.com/quickstart/" rel="noopener noreferrer"&gt;official WireGuard documentation&lt;/a&gt; and found it more than sufficient. Make sure this is complete before setting up clients! Windows and MacOS have &lt;a href="https://www.wireguard.com/install/" rel="noopener noreferrer"&gt;dedicated programs&lt;/a&gt; for clients, but Linux is a bit more complicated; hence this guide.&lt;/p&gt;




&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;WireGuard server, configured with port forwarding on your home router.&lt;/li&gt;
&lt;li&gt;Linux client with &lt;code&gt;sudo&lt;/code&gt; access (tested here on Mint/Debian).&lt;/li&gt;
&lt;li&gt;Basic command line familiarity.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Client Setup
&lt;/h1&gt;

&lt;h2&gt;
  
  
  SSH Key Generation
&lt;/h2&gt;

&lt;p&gt;This process should be familiar (for general server access, not WireGuard-specific handshakes), but direction is provided below just in case. On the client, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"your-email@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hit enter twice - use default location and no passkey (unless you really want one).&lt;/p&gt;

&lt;p&gt;There are two ways to copy your key to the server, the &lt;strong&gt;Easy Way&lt;/strong&gt; and the &lt;strong&gt;More Likely Way&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Easy Way:
&lt;/h3&gt;

&lt;p&gt;The easy way only works if your client can access the server. Since we are generating keys, you should probably have password authentication enabled for SSH. In case you forgot, you modify these lines in &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;PasswordAuthentication no
PubkeyAuthentication &lt;span class="nb"&gt;yes
&lt;/span&gt;PermitRootLogin no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then on the client side you can simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-copy-id username@server-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This copies SSH key settings to the host, if security settings allow it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The More Likely Way:
&lt;/h3&gt;

&lt;p&gt;Run this on the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/id_ed25519.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get this clipboard item to the server however you like, probably using another system that does have SSH access, and append it to &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt;. No need to disable the server's security requirements this way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Run on the client to install necessary dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;wireguard resolvconf netcat-openbsd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that is done, generate WireGuard keys (these are different than SSH keys - the former are needed to access the WireGuard server for configuration):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /etc/wireguard
&lt;span class="nb"&gt;sudo umask &lt;/span&gt;077
&lt;span class="nb"&gt;sudo &lt;/span&gt;wg genkey | &lt;span class="nb"&gt;sudo tee &lt;/span&gt;privatekey | &lt;span class="nb"&gt;sudo &lt;/span&gt;wg pubkey | &lt;span class="nb"&gt;sudo tee &lt;/span&gt;publickey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create the WireGuard config file on your client using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/wireguard/wg0.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And insert the following text to create your client's WireGuard network configuration file. This facilitates the key handshake your client system will do with the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;Interface]
PrivateKey &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c"&gt;# Paste the contents of /etc/wireguard/privatekey here&lt;/span&gt;
Address &lt;span class="o"&gt;=&lt;/span&gt; 10.0.0.2/24
DNS &lt;span class="o"&gt;=&lt;/span&gt; 1.1.1.1
MTU &lt;span class="o"&gt;=&lt;/span&gt; 1200

&lt;span class="o"&gt;[&lt;/span&gt;Peer]
PublicKey &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c"&gt;# Your server's public key goes here.&lt;/span&gt;
Endpoint &lt;span class="o"&gt;=&lt;/span&gt; your-server-ip:51820
AllowedIPs &lt;span class="o"&gt;=&lt;/span&gt; 192.168.1.0/24, 10.0.0.0/24
PersistentKeepalive &lt;span class="o"&gt;=&lt;/span&gt; 25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that you have three items to fill in above. You will need the PublicKey from the server, not from what you have here on the client. So hold off on that for now.&lt;/p&gt;

&lt;p&gt;MTU is optional, but I found that on Xfinity's public network it is necessary. A niche problem I spent a while debugging before &lt;a href="https://arnesonium.com/2024/11/using-wireguard-over-xfinitywifi" rel="noopener noreferrer"&gt;finding the solution&lt;/a&gt;. The split tunneling at AllowedIPs only routes home traffic through your home network, so you can access cameras and local infrastructure. All other traffic will not route through the VPN - this is much faster and best practice. If slow speed is OK or all traffic needs to route through the VPN for some other reason, &lt;code&gt;AllowedIPs&lt;/code&gt; can be &lt;code&gt;0.0.0.0/0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The private key on the client can be acquired using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /etc/wireguard/privatekey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The public key on the client (which will be needed on the server) can be acquired using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /etc/wireguard/publickey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now SSH into the WireGuard server, and edit the WireGuard config using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/wireguard/wg0.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Append the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;Peer]
PublicKey &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c"&gt;# Paste your client's public key here.&lt;/span&gt;
AllowedIPs &lt;span class="o"&gt;=&lt;/span&gt; 10.0.0.x/32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;AllowedIPs&lt;/code&gt;, you will need to replace the &lt;code&gt;x&lt;/code&gt;. For my setup, the server was .1, and I had two previous clients taking up .2 and .3, so I used .4. These are the IPs the WireGuard server uses to identify the peers on its network.&lt;/p&gt;

&lt;p&gt;Back on the client, edit the client's WireGuard config using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/wireguard/wg0.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember how we didn't have the server's public key last time? Fix that now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;Interface]
PrivateKey &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c"&gt;# Paste(d) the contents of /etc/wireguard/privatekey here&lt;/span&gt;
Address &lt;span class="o"&gt;=&lt;/span&gt; 10.0.0.2/24
DNS &lt;span class="o"&gt;=&lt;/span&gt; 1.1.1.1
MTU &lt;span class="o"&gt;=&lt;/span&gt; 1200

&lt;span class="o"&gt;[&lt;/span&gt;Peer]
PublicKey &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c"&gt;# Your server's public key goes here.&lt;/span&gt;
Endpoint &lt;span class="o"&gt;=&lt;/span&gt; your-server-ip:51820
AllowedIPs &lt;span class="o"&gt;=&lt;/span&gt; 192.168.1.0/24, 10.0.0.0/24
PersistentKeepalive &lt;span class="o"&gt;=&lt;/span&gt; 25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three items to check, above. Make sure the &lt;code&gt;Address&lt;/code&gt; on your client side matches &lt;code&gt;AllowedIps&lt;/code&gt; on the server side.&lt;/p&gt;




&lt;h1&gt;
  
  
  Automated VPN Management
&lt;/h1&gt;

&lt;p&gt;Run this first to set the WireGuard client as disabled by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl disable wg-quick@wg0
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop wg-quick@wg0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/NetworkManager/dispatcher.d/99-wireguard-auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the script text with the following, adjusting the &lt;code&gt;HOME_*&lt;/code&gt; values as needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;INTERFACE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;ACTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;

&lt;span class="c"&gt;# Configuration. Change these as needed.&lt;/span&gt;
&lt;span class="nv"&gt;HOME_NETWORK_GATEWAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"192.168.1.1"&lt;/span&gt; &lt;span class="c"&gt;# Router's IP.&lt;/span&gt;
&lt;span class="nv"&gt;HOME_WIREGUARD_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"192.168.1.114"&lt;/span&gt; &lt;span class="c"&gt;# WireGuard server IP.&lt;/span&gt;
&lt;span class="nv"&gt;WIREGUARD_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"51820"&lt;/span&gt;
&lt;span class="nv"&gt;HOME_EXTERNAL_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR.EXTERNAL.IP"&lt;/span&gt; &lt;span class="c"&gt;# Your external home network IP.&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/wireguard-auto.log"&lt;/span&gt;

&lt;span class="nv"&gt;PING_TIMEOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5
&lt;span class="nv"&gt;NC_TIMEOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10

log_message&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: [&lt;/span&gt;&lt;span class="nv"&gt;$INTERFACE&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$ACTION&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

is_wireguard_server_reachable&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;server_ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; nc &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        if &lt;/span&gt;nc &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; 5 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$server_ip&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;log_message &lt;span class="s2"&gt;"Server &lt;/span&gt;&lt;span class="nv"&gt;$server_ip&lt;/span&gt;&lt;span class="s2"&gt; is reachable and responding"&lt;/span&gt;
            &lt;span class="k"&gt;return &lt;/span&gt;0
        &lt;span class="k"&gt;else
            &lt;/span&gt;log_message &lt;span class="s2"&gt;"WireGuard port &lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="s2"&gt; on &lt;/span&gt;&lt;span class="nv"&gt;$server_ip&lt;/span&gt;&lt;span class="s2"&gt; is not responding"&lt;/span&gt;
            &lt;span class="k"&gt;return &lt;/span&gt;1
        &lt;span class="k"&gt;fi
    else&lt;/span&gt;
        &lt;span class="c"&gt;# Fallback if nc is not available.&lt;/span&gt;
        log_message &lt;span class="s2"&gt;"Error: nc not available"&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;1
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

is_home_network&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 3 &lt;span class="nt"&gt;-W&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PING_TIMEOUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME_NETWORK_GATEWAY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        if &lt;/span&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 3 &lt;span class="nt"&gt;-W&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PING_TIMEOUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME_WIREGUARD_SERVER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nv"&gt;local_ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ip route get 8.8.8.8 2&amp;gt;/dev/null | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $7; exit}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$local_ip&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^192&lt;span class="se"&gt;\.&lt;/span&gt;168&lt;span class="se"&gt;\.&lt;/span&gt;1&lt;span class="se"&gt;\.&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
                return &lt;/span&gt;0
            &lt;span class="k"&gt;fi
        fi
    fi
    return &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

enable_wireguard&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; systemctl is-active &lt;span class="nt"&gt;--quiet&lt;/span&gt; wg-quick@wg0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;log_message &lt;span class="s2"&gt;"Enabling WireGuard"&lt;/span&gt;
        systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;wg-quick@wg0
        systemctl start wg-quick@wg0
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

disable_wireguard&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;systemctl is-active &lt;span class="nt"&gt;--quiet&lt;/span&gt; wg-quick@wg0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;log_message &lt;span class="s2"&gt;"Disabling WireGuard"&lt;/span&gt;
        systemctl disable wg-quick@wg0
        systemctl stop wg-quick@wg0
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Only act on interface up events.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"up"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;log_message &lt;span class="s2"&gt;"Network change detected on &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;sleep &lt;/span&gt;10

&lt;span class="k"&gt;if &lt;/span&gt;is_home_network&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log_message &lt;span class="s2"&gt;"Home network confirmed - ensuring WireGuard is disabled"&lt;/span&gt;
    disable_wireguard
&lt;span class="k"&gt;else
    &lt;/span&gt;log_message &lt;span class="s2"&gt;"External network confirmed - testing VPN server"&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;is_wireguard_server_reachable &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME_EXTERNAL_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WIREGUARD_PORT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;log_message &lt;span class="s2"&gt;"VPN server reachable - enabling WireGuard"&lt;/span&gt;
        enable_wireguard
    &lt;span class="k"&gt;else
        &lt;/span&gt;log_message &lt;span class="s2"&gt;"VPN server unreachable - WireGuard remains disabled"&lt;/span&gt;
        disable_wireguard
    &lt;span class="k"&gt;fi
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, run &lt;code&gt;sudo vim /etc/systemd/system/wireguard-boot-check.service&lt;/code&gt; and add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;WireGuard Boot Config
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network-online.target
&lt;span class="nv"&gt;Wants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network-online.target
&lt;span class="nv"&gt;Before&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wg-quick@wg0.service

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oneshot
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/bin/wireguard-boot-check.sh
&lt;span class="nv"&gt;RemainAfterExit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes
&lt;/span&gt;&lt;span class="nv"&gt;TimeoutStartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;150

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;sudo vim /usr/local/bin/wireguard-boot-check.sh&lt;/code&gt; and add the following adjusting variables as needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Configuration - adjust these to match your setup.&lt;/span&gt;
&lt;span class="nv"&gt;HOME_NETWORK_GATEWAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"192.168.1.1"&lt;/span&gt;
&lt;span class="nv"&gt;HOME_WIREGUARD_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"192.168.1.114"&lt;/span&gt;
&lt;span class="nv"&gt;HOME_EXTERNAL_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR.EXTERNAL.IP.HERE"&lt;/span&gt;
&lt;span class="nv"&gt;WIREGUARD_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"51820"&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/wireguard-boot-check.log"&lt;/span&gt;

&lt;span class="c"&gt;# Timeout after 2 min.&lt;/span&gt;
&lt;span class="nv"&gt;NETWORK_WAIT_TIMEOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;120
&lt;span class="nv"&gt;PING_TIMEOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5
&lt;span class="nv"&gt;NC_TIMEOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10

log_message&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

is_wireguard_server_reachable&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;server_ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="c"&gt;# Give network time to settle on boot.&lt;/span&gt;
    &lt;span class="nb"&gt;sleep &lt;/span&gt;10
    check_timeout

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; nc &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        if &lt;/span&gt;nc &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; 5 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$server_ip&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;log_message &lt;span class="s2"&gt;"Server &lt;/span&gt;&lt;span class="nv"&gt;$server_ip&lt;/span&gt;&lt;span class="s2"&gt; is reachable and responding"&lt;/span&gt;
            &lt;span class="k"&gt;return &lt;/span&gt;0
        &lt;span class="k"&gt;else
            &lt;/span&gt;log_message &lt;span class="s2"&gt;"WireGuard port &lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="s2"&gt; on &lt;/span&gt;&lt;span class="nv"&gt;$server_ip&lt;/span&gt;&lt;span class="s2"&gt; is not responding"&lt;/span&gt;
            &lt;span class="k"&gt;return &lt;/span&gt;1
        &lt;span class="k"&gt;fi
    else&lt;/span&gt;
        &lt;span class="c"&gt;# Fallback if nc is not available.&lt;/span&gt;
        log_message &lt;span class="s2"&gt;"Error: nc not available"&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;1
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

is_home_network&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# Check if home gateway is reachable.&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 3 &lt;span class="nt"&gt;-W&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PING_TIMEOUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME_NETWORK_GATEWAY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="c"&gt;# Second check by pinging WireGuard server directly.&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 3 &lt;span class="nt"&gt;-W&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PING_TIMEOUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME_WIREGUARD_SERVER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
            &lt;span class="c"&gt;# Third check by seeing if client IP is in the home range.&lt;/span&gt;
            &lt;span class="nv"&gt;local_ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ip route get 8.8.8.8 2&amp;gt;/dev/null | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $7; exit}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$local_ip&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^192&lt;span class="se"&gt;\.&lt;/span&gt;168&lt;span class="se"&gt;\.&lt;/span&gt;1&lt;span class="se"&gt;\.&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
                &lt;/span&gt;log_message &lt;span class="s2"&gt;"Confirmed home network (gateway: &lt;/span&gt;&lt;span class="nv"&gt;$HOME_NETWORK_GATEWAY&lt;/span&gt;&lt;span class="s2"&gt;, server: &lt;/span&gt;&lt;span class="nv"&gt;$HOME_WIREGUARD_SERVER&lt;/span&gt;&lt;span class="s2"&gt;, local IP: &lt;/span&gt;&lt;span class="nv"&gt;$local_ip&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
                &lt;span class="k"&gt;return &lt;/span&gt;0
            &lt;span class="k"&gt;fi
        fi
    fi
    &lt;/span&gt;log_message &lt;span class="s2"&gt;"Not on home network"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

enable_wireguard&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    log_message &lt;span class="s2"&gt;"Enabling WireGuard"&lt;/span&gt;
    systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;wg-quick@wg0
    systemctl start wg-quick@wg0
&lt;span class="o"&gt;}&lt;/span&gt;

log_message &lt;span class="s2"&gt;"Boot check: Starting (WireGuard disabled by default)"&lt;/span&gt;

&lt;span class="c"&gt;# Wait for stable network connectivity.&lt;/span&gt;
&lt;span class="nv"&gt;elapsed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$elapsed&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; &lt;span class="nv"&gt;$NETWORK_WAIT_TIMEOUT&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if &lt;/span&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 &lt;span class="nt"&gt;-W&lt;/span&gt; 3 8.8.8.8 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;log_message &lt;span class="s2"&gt;"Network connectivity established after &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;elapsed&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s"&lt;/span&gt;
        &lt;span class="nb"&gt;break
    &lt;/span&gt;&lt;span class="k"&gt;fi
    &lt;/span&gt;log_message &lt;span class="s2"&gt;"Waiting for network connectivity... (&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;elapsed&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s)"&lt;/span&gt;
    &lt;span class="nb"&gt;sleep &lt;/span&gt;10
    &lt;span class="nv"&gt;elapsed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;elapsed &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# If still no connectivity, exit.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 &lt;span class="nt"&gt;-W&lt;/span&gt; 3 8.8.8.8 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log_message &lt;span class="s2"&gt;"No network connectivity after &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;elapsed&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s - WireGuard remains disabled"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;log_message &lt;span class="s2"&gt;"Allowing network to settle..."&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;15

&lt;span class="c"&gt;# Network location detection.&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;is_home_network&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log_message &lt;span class="s2"&gt;"Home network confirmed - WireGuard remains disabled"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;log_message &lt;span class="s2"&gt;"External network confirmed - testing VPN server accessibility"&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;is_wireguard_server_reachable &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME_EXTERNAL_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WIREGUARD_PORT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;log_message &lt;span class="s2"&gt;"VPN server confirmed reachable - enabling WireGuard"&lt;/span&gt;
        enable_wireguard
    &lt;span class="k"&gt;else
        &lt;/span&gt;log_message &lt;span class="s2"&gt;"VPN server not accessible - WireGuard remains disabled"&lt;/span&gt;
    &lt;span class="k"&gt;fi
fi

&lt;/span&gt;log_message &lt;span class="s2"&gt;"Boot check: WireGuard configuration complete"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make the script executable and enable it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/wireguard-boot-check.sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;wireguard-boot-check.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/wireguard/wg0.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And add this to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;Interface]
PrivateKey &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c"&gt;# Your laptop's private key.&lt;/span&gt;
Address &lt;span class="o"&gt;=&lt;/span&gt; 10.0.0.2/24
DNS &lt;span class="o"&gt;=&lt;/span&gt; 1.1.1.1 &lt;span class="c"&gt;# Or 192.168.1.xxx for Pi-Hole.&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;Peer]
PublicKey &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c"&gt;# Your server's public key.&lt;/span&gt;
Endpoint &lt;span class="o"&gt;=&lt;/span&gt; your-server-external-ip:51820
AllowedIPs &lt;span class="o"&gt;=&lt;/span&gt; 0.0.0.0/0
PersistentKeepalive &lt;span class="o"&gt;=&lt;/span&gt; 25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note for Endpoint above, you need your server's external IP. This will be your home router's IP, port forwarded to your VPN server.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verification
&lt;/h2&gt;

&lt;p&gt;After setup is complete, verify your VPN is working correctly.&lt;/p&gt;

&lt;p&gt;On an 'away' network, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;wg show
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status wg-quick@wg0
ip addr show wg0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your WireGuard client configuration in the output.&lt;/p&gt;

&lt;p&gt;You should also see your home external IP displayed when you try to view your client's public IP. Verify this using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; ifconfig.me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify DNS resolution using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nslookup google.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs should also populate as your client switches networks or reboots. This can be viewed using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/wireguard-auto.log
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/wireguard-boot-check.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;That should get you a working VPN client! If you notice any issues with this guide, please feel free to reach out or leave a comment.&lt;/p&gt;




&lt;h1&gt;
  
  
  Appendix
&lt;/h1&gt;

&lt;p&gt;The old (depreciated) method can be viewed &lt;a href="https://portfolio.rothellc.com/blog/2025/setting-up-a-wireguard-vpn-client-on-linux/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>vpn</category>
      <category>wireguard</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
