<?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: Rev</title>
    <description>The latest articles on DEV Community by Rev (@revsystem).</description>
    <link>https://dev.to/revsystem</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%2F325441%2F285a88df-c8fc-4169-bc0a-78e0f3e90fc8.png</url>
      <title>DEV Community: Rev</title>
      <link>https://dev.to/revsystem</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/revsystem"/>
    <language>en</language>
    <item>
      <title>Setting Up an L2TP/IPsec Remote Access VPN on EdgeRouter X</title>
      <dc:creator>Rev</dc:creator>
      <pubDate>Tue, 28 Apr 2026 15:46:56 +0000</pubDate>
      <link>https://dev.to/revsystem/setting-up-an-l2tpipsec-remote-access-vpn-on-edgerouter-x-23e0</link>
      <guid>https://dev.to/revsystem/setting-up-an-l2tpipsec-remote-access-vpn-on-edgerouter-x-23e0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the previous article, &lt;a href="https://dev.to/aws-builders/running-an-aws-lambda-route-53-ddns-client-on-edgerouter-x-24f0"&gt;Running an AWS Lambda + Route 53 DDNS Client on EdgeRouter X&lt;/a&gt;, I built a system that periodically runs a DDNS client on the EdgeRouter X to keep a Route 53 DNS record in sync with the global IP address assigned to its WAN interface. This made it possible to associate an FQDN with the dynamically changing global IP address. In this article, I use that setup to connect to the EdgeRouter X via remote access VPN.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://yabe.jp/gadgets/edgerouter-x-06-l2tp/" rel="noopener noreferrer"&gt;https://yabe.jp/gadgets/edgerouter-x-06-l2tp/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iodata.jp/support/qanda/answer/s33672.htm" rel="noopener noreferrer"&gt;https://www.iodata.jp/support/qanda/answer/s33672.htm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test Environment
&lt;/h2&gt;

&lt;p&gt;Tested on EdgeRouter X (ER-X) firmware &lt;a href="https://community.ui.com/releases/EdgeRouter-3-0-1/7fe6b39d-baea-4ce6-87a0-5dcdc9538c3a" rel="noopener noreferrer"&gt;3.0.1&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network Topology
&lt;/h2&gt;

&lt;p&gt;The diagram below is a simplified view of the configuration that connects Site A and Site B via VPN.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph LR
    subgraph SiteA["Site A (192.168.10.0/24)"]
        PC[PC] --- L2SWA[L2 Switch] --- RouterA[Router]
    end

    subgraph SiteB["Site B (192.168.1.0/24)"]
        ERX[EdgeRouter X] --- L2SW[L2 Switch]
        L2SW --- NAS[NAS]
        L2SW --- RPI[Raspberry Pi]
        L2SW --- AP[Wireless AP]
        AP -.-|Wireless| IoT1[IoT Device 1]
        AP -.-|Wireless| IoT2[IoT Device 2]
        AP -.-|Wireless| IoT3[IoT Device 3]
    end

    RouterA ===|VPN| ERX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring L2TP/IPsec
&lt;/h2&gt;

&lt;p&gt;I configured L2TP/IPsec by following &lt;a href="https://yabe.jp/gadgets/edgerouter-x-06-l2tp/" rel="noopener noreferrer"&gt;EdgeRouter X – 6. リモートアクセス VPN (L2TP)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Open the CLI window from the EdgeRouter X GUI, or access the router via SSH.&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;c&lt;span class="o"&gt;)&lt;/span&gt; 2010-2023
 |  _| / _  |/ _  |/ _ &lt;span class="se"&gt;\ &lt;/span&gt;        Ubiquiti Inc.
 | |__| &lt;span class="o"&gt;(&lt;/span&gt;_| | &lt;span class="o"&gt;(&lt;/span&gt;_| |  __/
 |_____&lt;span class="se"&gt;\_&lt;/span&gt;_._|&lt;span class="se"&gt;\_&lt;/span&gt;_. |&lt;span class="se"&gt;\_&lt;/span&gt;__|         https://www.ui.com
             |___/

Welcome to EdgeOS

By logging &lt;span class="k"&gt;in&lt;/span&gt;, accessing, or using the Ubiquiti product, you
acknowledge that you have &lt;span class="nb"&gt;read &lt;/span&gt;and understood the Ubiquiti
License Agreement &lt;span class="o"&gt;(&lt;/span&gt;available &lt;span class="k"&gt;in &lt;/span&gt;the Web UI at, by default,
http://192.168.1.1&lt;span class="o"&gt;)&lt;/span&gt; and agree to be bound by its terms.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter configuration mode:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;In configuration mode, run the following commands to configure L2TP/IPsec:&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;## Basic IPsec configuration&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;vpn ipsec ipsec-interfaces interface eth0
&lt;span class="nb"&gt;set &lt;/span&gt;vpn ipsec nat-networks allowed-network 0.0.0.0/0
&lt;span class="nb"&gt;set &lt;/span&gt;vpn ipsec nat-traversal &lt;span class="nb"&gt;enable
set &lt;/span&gt;vpn ipsec auto-firewall-nat-exclude &lt;span class="nb"&gt;enable&lt;/span&gt;

&lt;span class="c"&gt;## Specify that the L2TP WAN-side IP address is assigned via DHCP&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;vpn l2tp remote-access dhcp-interface eth0

&lt;span class="c"&gt;## Specify the IP address range assigned to L2TP clients&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;vpn l2tp remote-access client-ip-pool start 192.168.1.100
&lt;span class="nb"&gt;set &lt;/span&gt;vpn l2tp remote-access client-ip-pool stop 192.168.1.200

&lt;span class="c"&gt;## Set the L2TP "secret"&lt;/span&gt;
&lt;span class="c"&gt;## Replace YOUR_SECRET with any password of your choice&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;vpn l2tp remote-access ipsec-settings authentication mode pre-shared-secret
&lt;span class="nb"&gt;set &lt;/span&gt;vpn l2tp remote-access ipsec-settings authentication pre-shared-secret YOUR_SECRET
&lt;span class="nb"&gt;set &lt;/span&gt;vpn l2tp remote-access ipsec-settings ike-lifetime 3600

&lt;span class="c"&gt;## Set L2TP client authentication to local&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;vpn l2tp remote-access authentication mode &lt;span class="nb"&gt;local&lt;/span&gt;

&lt;span class="c"&gt;## Set the L2TP client username and password&lt;/span&gt;
&lt;span class="c"&gt;## Replace YOUR_USERNAME and YOUR_PASSWORD with your values&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;vpn l2tp remote-access authentication local-users username YOUR_USERNAME password YOUR_PASSWORD

&lt;span class="c"&gt;## Set the L2TP MTU conservatively&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;vpn l2tp remote-access mtu 1280

&lt;span class="c"&gt;## Set the DNS used by L2TP clients to the router itself and Google DNS&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;vpn l2tp remote-access dns-servers server-1 192.168.1.1
&lt;span class="nb"&gt;set &lt;/span&gt;vpn l2tp remote-access dns-servers server-2 8.8.8.8

&lt;span class="c"&gt;## Enable DNS forwarding on the router so that L2TP clients can use the router's DNS&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;service dns forwarding listen-on lo

commit
save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Firewall Configuration on the WAN-Side Interface
&lt;/h2&gt;

&lt;p&gt;Next, configure the firewall so that L2TP/IPsec connections from the WAN side can reach the EdgeRouter X. This is also based on the sites listed in the References section.&lt;/p&gt;

&lt;p&gt;Log in to the EdgeRouter X GUI as an administrator and open the firewall settings. Click the &lt;code&gt;Firewall/NAT&lt;/code&gt; icon in the left-side menu.&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%2Frvqj7ulenxpwybt776t9.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%2Frvqj7ulenxpwybt776t9.png" alt="Firewall/NAT" width="362" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the &lt;code&gt;Firewall Policies&lt;/code&gt; tab → &lt;code&gt;Actions&lt;/code&gt; for &lt;code&gt;WAN_LOCAL&lt;/code&gt; → &lt;code&gt;Edit Ruleset&lt;/code&gt;.&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%2F0akx2d0pz82kfixbn0nn.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%2F0akx2d0pz82kfixbn0nn.png" alt="Edit Ruleset" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Add New Rule&lt;/code&gt;. (The screenshot below shows the state after the rules described below have already been added.)&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%2Fteg1qqlidzvd55sdu56b.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%2Fteg1qqlidzvd55sdu56b.png" alt="Ruleset Configurations for WAN_LOCAL" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the &lt;code&gt;Basic&lt;/code&gt; tab. Enter any name in &lt;code&gt;Description&lt;/code&gt;. Here, I use &lt;code&gt;Allow L2TP&lt;/code&gt;. For &lt;code&gt;Action&lt;/code&gt;, select &lt;code&gt;Accept&lt;/code&gt;, and for &lt;code&gt;Protocol&lt;/code&gt;, select &lt;code&gt;UDP&lt;/code&gt;.&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%2Fdg2otbuewyx6nm5um552.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%2Fdg2otbuewyx6nm5um552.png" alt="Basic tab for L2TP rule" width="554" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click the &lt;code&gt;Destination&lt;/code&gt; tab and enter &lt;code&gt;500,1701,4500&lt;/code&gt; in &lt;code&gt;Port&lt;/code&gt;. Finally, click &lt;code&gt;Save&lt;/code&gt;.&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%2Fdvqtwl26m3v7vrzykb0u.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%2Fdvqtwl26m3v7vrzykb0u.png" alt="Destination tab for L2TP rule" width="559" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, click &lt;code&gt;Add New Rule&lt;/code&gt; again.&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%2Fteg1qqlidzvd55sdu56b.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%2Fteg1qqlidzvd55sdu56b.png" alt="Ruleset Configurations for WAN_LOCAL" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the &lt;code&gt;Basic&lt;/code&gt; tab. Enter any name in &lt;code&gt;Description&lt;/code&gt;. Here, I use &lt;code&gt;Allow ESP&lt;/code&gt;. For &lt;code&gt;Action&lt;/code&gt;, select &lt;code&gt;Accept&lt;/code&gt;. For &lt;code&gt;Protocol&lt;/code&gt;, select &lt;code&gt;Choose a protocol by name&lt;/code&gt;, and choose &lt;code&gt;esp&lt;/code&gt; from the dropdown. Finally, click &lt;code&gt;Save&lt;/code&gt;.&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%2Fxla7myafi174z6h2qffr.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%2Fxla7myafi174z6h2qffr.png" alt="Basic tab for ESP rule" width="558" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Move the two newly added rules so they sit between the existing &lt;code&gt;Allow established/related&lt;/code&gt; rule and the &lt;code&gt;Drop invalid state&lt;/code&gt; rule. You can drag and drop each rule by hovering over it.&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%2Fs8a5m44ls8ixl66lgrwl.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%2Fs8a5m44ls8ixl66lgrwl.png" alt="Ruleset Configurations for WAN_LOCAL" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the VPN Client on Windows 11
&lt;/h2&gt;

&lt;p&gt;My Windows 11 system is set to English, so the menus and buttons appear in English. The screen layout and button positions are the same in the Japanese version, so substitute as needed.&lt;/p&gt;

&lt;p&gt;In the Settings app, click &lt;code&gt;Network &amp;amp; Internet&lt;/code&gt; → &lt;code&gt;VPN&lt;/code&gt; → &lt;code&gt;Add VPN&lt;/code&gt;.&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%2F1a5kyv80v1cx6ceji74c.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%2F1a5kyv80v1cx6ceji74c.png" alt="Network &amp;amp; Internet settings" width="758" height="443"&gt;&lt;/a&gt;&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%2Fel6oa8rimakrl2lwfwug.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%2Fel6oa8rimakrl2lwfwug.png" alt="Add a VPN connection" width="758" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill in each field by referring to the table below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VPN Type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;L2TP/IPsec with pre-shared key&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type of sign-in info&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Username and password&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection name&lt;/td&gt;
&lt;td&gt;Any name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server name or address&lt;/td&gt;
&lt;td&gt;The FQDN configured via DDNS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-shared key&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;YOUR_SECRET&lt;/code&gt; set in the L2TP/IPsec configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Username&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;YOUR_USERNAME&lt;/code&gt; set in the L2TP/IPsec configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;YOUR_PASSWORD&lt;/code&gt; set in the L2TP/IPsec configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2F6fce2bp4l7ae30vwcdw3.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%2F6fce2bp4l7ae30vwcdw3.png" alt="Add a VPN connection form" width="758" height="754"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the VPN connection you just created, click &lt;code&gt;Advanced options&lt;/code&gt;.&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%2Fmqdgpo5x32m6szlu7ez5.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%2Fmqdgpo5x32m6szlu7ez5.png" alt="Advanced options" width="758" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Edit&lt;/code&gt; next to &lt;code&gt;More VPN properties&lt;/code&gt;.&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%2F0rrsbf6pm25o01si8bd6.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%2F0rrsbf6pm25o01si8bd6.png" alt="More VPN properties" width="758" height="768"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the &lt;code&gt;Security&lt;/code&gt; tab, select &lt;code&gt;Allow these protocols&lt;/code&gt;, and check &lt;code&gt;Microsoft CHAP Version 2 (MS-CHAP v2)&lt;/code&gt;.&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%2Fc6kqta0bxfqn5ywuzqf2.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%2Fc6kqta0bxfqn5ywuzqf2.png" alt="Security tab" width="363" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the &lt;code&gt;Networking&lt;/code&gt; tab, select &lt;code&gt;Internet Protocol Version 4 (TCP/IPv4)&lt;/code&gt;, and click &lt;code&gt;Properties&lt;/code&gt;.&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%2Fmotbfjqr6qs3buh8ydh2.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%2Fmotbfjqr6qs3buh8ydh2.png" alt="Networking tab" width="363" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the &lt;code&gt;General&lt;/code&gt; tab, click &lt;code&gt;Advanced&lt;/code&gt;.&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%2Fkkmzby12yt2rc4ti15ub.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%2Fkkmzby12yt2rc4ti15ub.png" alt="General tab" width="541" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the &lt;code&gt;IP Settings&lt;/code&gt; tab, uncheck &lt;code&gt;Use default gateway on remote network&lt;/code&gt;.&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%2Fu9xw3a72mcxljyiuom3l.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%2Fu9xw3a72mcxljyiuom3l.png" alt="IP Settings tab" width="400" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying the Connection
&lt;/h2&gt;

&lt;p&gt;Click &lt;code&gt;Connect&lt;/code&gt;.&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%2Flsqpaqy1kxc7ghpjsy0k.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%2Flsqpaqy1kxc7ghpjsy0k.png" alt="Connect button" width="758" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the connection succeeds, the connection details are displayed as follows.&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%2Fdqevo1zzkwbjz9lvaex9.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%2Fdqevo1zzkwbjz9lvaex9.png" alt="Connected successfully" width="758" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you can reach network devices behind the VPN, such as a NAS, the connection is working. Internet traffic is routed through the WAN side of the EdgeRouter X. You can also access the LAN-side IP address of the EdgeRouter X from the VPN client to check the status of the remote site.&lt;/p&gt;

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

&lt;p&gt;In this article, I covered how to connect to an EdgeRouter X via remote access VPN, specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;L2TP/IPsec configuration on the EdgeRouter X&lt;/li&gt;
&lt;li&gt;Firewall configuration on the WAN-side interface&lt;/li&gt;
&lt;li&gt;VPN client configuration on Windows 11&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Windows 11 VPN client setup was a bit involved. The VPN properties span several screens, and a few options are not particularly intuitive.&lt;/p&gt;

&lt;p&gt;For EdgeRouter X configuration in general, the &lt;a href="https://yabe.jp/gadgets/edgerouter-x/" rel="noopener noreferrer"&gt;EdgeRouter X がすごい&lt;/a&gt; series is a great reference. It contains many practical configuration examples, so I recommend it as a reference.&lt;/p&gt;

&lt;p&gt;By combining the previous article, &lt;a href="https://dev.to/aws-builders/running-an-aws-lambda-route-53-ddns-client-on-edgerouter-x-24f0"&gt;Running an AWS Lambda + Route 53 DDNS Client on EdgeRouter X&lt;/a&gt;, with the configuration described here, you can connect to your EdgeRouter X via remote access VPN. I hope this article is helpful.&lt;/p&gt;




&lt;h3&gt;
  
  
  Translation notes
&lt;/h3&gt;

&lt;p&gt;A few stylistic decisions I made to match your previous dev.to post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Headings&lt;/strong&gt;: Used &lt;code&gt;## Introduction&lt;/code&gt;, &lt;code&gt;## References&lt;/code&gt;, &lt;code&gt;## Test Environment&lt;/code&gt;, and &lt;code&gt;## Conclusion&lt;/code&gt;, identical to the prior post.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice&lt;/strong&gt;: First-person ("I built", "I use") for narrative sections and imperative ("Click", "Run") for procedural steps, matching your earlier article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminology&lt;/strong&gt;: Kept "shared secret", "Lambda function URL", "FQDN", "WAN side" consistent with the previous translation. "拠点 A / 拠点 B" → "Site A / Site B" to match common networking phrasing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;References&lt;/strong&gt;: Listed as bullet points with &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt;-wrapped URLs, the same format as before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Title&lt;/strong&gt;: Used &lt;code&gt;Setting Up an L2TP/IPsec Remote Access VPN on EdgeRouter X&lt;/code&gt; to mirror the gerund + "on EdgeRouter X" style of the prior title (&lt;code&gt;Running an AWS Lambda + Route 53 DDNS Client on EdgeRouter X&lt;/code&gt;). If you prefer something closer to the literal Japanese, &lt;code&gt;Connecting to EdgeRouter X via Remote Access VPN&lt;/code&gt; also works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image links&lt;/strong&gt;: Left the Qiita-hosted image URLs intact. They are publicly accessible from S3, so they should render on dev.to as-is. If you want to host them on dev.to instead, you'll need to re-upload via the dev.to editor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know if you'd like a different title, a more literal translation of any specific section, or if you want me to save this as a file in the repo (e.g., outside &lt;code&gt;public/&lt;/code&gt; so the Qiita CLI doesn't pick it up).&lt;/p&gt;

</description>
      <category>aws</category>
      <category>edgerouterx</category>
      <category>tutorial</category>
      <category>vpn</category>
    </item>
    <item>
      <title>I tried out "1-bit LLM Bonsai" using the Linux terminal app on my Google Pixel 7a.</title>
      <dc:creator>Rev</dc:creator>
      <pubDate>Wed, 15 Apr 2026 15:59:09 +0000</pubDate>
      <link>https://dev.to/revsystem/i-tried-on-1-bit-llm-bonsai-on-the-google-pixel-7as-linux-terminal-app-3a1h</link>
      <guid>https://dev.to/revsystem/i-tried-on-1-bit-llm-bonsai-on-the-google-pixel-7as-linux-terminal-app-3a1h</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I tried running PrismML's 1-bit LLMs, Bonsai-8B and Bonsai-1.7B, on the Linux terminal app on a Google Pixel 7a. Bonsai-8B has a compact model size of just 1.15GB and reportedly runs on a Raspberry Pi 4B. Since it was described as "&lt;a href="https://www.itmedia.co.jp/aiplus/articles/2604/06/news084.html" rel="noopener noreferrer"&gt;An 8-billion-parameter LLM that 'runs on a smartphone' — '1-bit Bonsai' at 1.15GB claiming production-level performance draws attention&lt;/a&gt;," I decided to test whether it actually runs on a smartphone.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://prismml.com/news/bonsai-8b" rel="noopener noreferrer"&gt;https://prismml.com/news/bonsai-8b&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/y0kud4/items/3f7faeea52d7eec01b0f" rel="noopener noreferrer"&gt;https://qiita.com/y0kud4/items/3f7faeea52d7eec01b0f&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/moritalous/items/96cdc8bcd48d8a193556" rel="noopener noreferrer"&gt;https://qiita.com/moritalous/items/96cdc8bcd48d8a193556&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/revsystem/items/7ad150a7f99aaea27691" rel="noopener noreferrer"&gt;https://qiita.com/revsystem/items/7ad150a7f99aaea27691&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zenn.dev/kun432/scraps/ce16474a3be277" rel="noopener noreferrer"&gt;https://zenn.dev/kun432/scraps/ce16474a3be277&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Environment
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Google Pixel 7a&lt;/li&gt;
&lt;li&gt;Android 16&lt;/li&gt;
&lt;li&gt;Linux terminal&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Enabling the Linux Terminal app
&lt;/h3&gt;

&lt;p&gt;To enable the Linux terminal app on a Pixel device, refer to the article &lt;a href="https://qiita.com/revsystem/items/7ad150a7f99aaea27691" rel="noopener noreferrer"&gt;Running AWS Bedrock API Using Google Pixel's Linux Terminal app&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building llama.cpp
&lt;/h3&gt;

&lt;p&gt;Build llama.cpp by following this article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/moritalous/items/96cdc8bcd48d8a193556" rel="noopener noreferrer"&gt;https://qiita.com/moritalous/items/96cdc8bcd48d8a193556&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install the packages required for the build:&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; &lt;span class="nt"&gt;-y&lt;/span&gt; git cmake build-essential libopenblas-dev pkg-config openssl libssl-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clone the llama.cpp fork maintained by PrismML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/PrismML-Eng/llama.cpp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;llama.cpp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build the project. This takes approximately 15 minutes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cmake &lt;span class="nt"&gt;-B&lt;/span&gt; build &lt;span class="nt"&gt;-DGGML_BLAS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON &lt;span class="nt"&gt;-DGGML_BLAS_VENDOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;OpenBLAS
cmake &lt;span class="nt"&gt;--build&lt;/span&gt; build &lt;span class="nt"&gt;--config&lt;/span&gt; Release &lt;span class="nt"&gt;-j2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the Models
&lt;/h3&gt;

&lt;p&gt;Since the Pixel's Linux terminal app does not support Japanese input, I used a simple prompt: 'hello'. Both models produced streaming output too slowly to be practical on the Pixel's Linux terminal app. Additionally, the Linux terminal app crashed frequently, requiring a full recovery each time, so I was only able to test the following two patterns.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bonsai-8B
&lt;/h4&gt;

&lt;p&gt;The first run takes longer because the model needs to be downloaded. When the context size was not specified with &lt;code&gt;-c&lt;/code&gt;, a Segmentation fault occurred. Following the advice at &lt;a href="https://zenn.dev/kun432/scraps/ce16474a3be277#comment-3592ca1fdb0075" rel="noopener noreferrer"&gt;https://zenn.dev/kun432/scraps/ce16474a3be277#comment-3592ca1fdb0075&lt;/a&gt;, I set the context size to 32784.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./build/bin/llama-cli &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-hf&lt;/span&gt; prism-ml/Bonsai-8B-gguf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; 32784
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F61809%2Fc0a2eed9-7413-4dbc-92fc-17897246a26f.png" class="article-body-image-wrapper"&gt;&lt;img width="587" alt="Bonsai-8B execution result" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F61809%2Fc0a2eed9-7413-4dbc-92fc-17897246a26f.png" height="1304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The results were as follows:&lt;br&gt;
[Prompt: 1.5 t/s | Generation: 0.1 t/s]&lt;/p&gt;
&lt;h4&gt;
  
  
  Bonsai-1.7B
&lt;/h4&gt;

&lt;p&gt;Similarly, I ran Bonsai-1.7B. This model did not require specifying the context size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./build/bin/llama-cli &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-hf&lt;/span&gt; prism-ml/Bonsai-1.7B-gguf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F61809%2Fe80adaa5-5398-4a15-b744-b78a67137cfc.png" class="article-body-image-wrapper"&gt;&lt;img width="587" alt="Bonsai-1.7B execution result" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F61809%2Fe80adaa5-5398-4a15-b744-b78a67137cfc.png" height="1304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The results were as follows:&lt;br&gt;
[Prompt: 4.4 t/s | Generation: 1.5 t/s]&lt;/p&gt;

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

&lt;p&gt;I tested Bonsai-8B and Bonsai-1.7B on the Google Pixel 7a's Linux terminal app. I confirmed that both models run. However, despite being lightweight models, the devices' performance was insufficient for practical use. That said, the fact that the build completed successfully was an interesting finding.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>pixel</category>
    </item>
    <item>
      <title>Running an AWS Lambda + Route 53 DDNS Client on EdgeRouter X</title>
      <dc:creator>Rev</dc:creator>
      <pubDate>Fri, 10 Apr 2026 18:07:44 +0000</pubDate>
      <link>https://dev.to/aws-builders/running-an-aws-lambda-route-53-ddns-client-on-edgerouter-x-24f0</link>
      <guid>https://dev.to/aws-builders/running-an-aws-lambda-route-53-ddns-client-on-edgerouter-x-24f0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;My home network is assigned a dynamic global IP address by the ISP, so DDNS is essential for inbound connections. I built a system that periodically updates DNS records from an &lt;a href="https://store.ui.com/us/en/products/er-x-sfp" rel="noopener noreferrer"&gt;EdgeRouter X&lt;/a&gt; using the serverless DDNS solution (Lambda + Route 53 + DynamoDB) published by AWS.&lt;/p&gt;

&lt;p&gt;This article covers the steps from setting up the AWS environment to configuring the client on the EdgeRouter X and verifying operation.&lt;/p&gt;

&lt;p&gt;The ultimate goal is to connect to the EdgeRouter X via remote access VPN using DDNS, but first we need to build the DDNS infrastructure.&lt;/p&gt;

&lt;p&gt;For details on how the solution works, see &lt;a href="https://aws.amazon.com/startups/learn/building-a-serverless-dynamic-dns-system-with-aws" rel="noopener noreferrer"&gt;Building a Serverless Dynamic DNS System with AWS&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/startups/learn/building-a-serverless-dynamic-dns-system-with-aws" rel="noopener noreferrer"&gt;https://aws.amazon.com/startups/learn/building-a-serverless-dynamic-dns-system-with-aws&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/awslabs/route53-dynamic-dns-with-lambda" rel="noopener noreferrer"&gt;https://github.com/awslabs/route53-dynamic-dns-with-lambda&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yabe.jp/gadgets/edgerouter-x-05-ddns/" rel="noopener noreferrer"&gt;https://yabe.jp/gadgets/edgerouter-x-05-ddns/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test Environment
&lt;/h2&gt;

&lt;p&gt;Tested on EdgeRouter X (ER-X) firmware &lt;a href="https://community.ui.com/releases/EdgeRouter-3-0-1/7fe6b39d-baea-4ce6-87a0-5dcdc9538c3a" rel="noopener noreferrer"&gt;3.0.1&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Forked Repository
&lt;/h2&gt;

&lt;p&gt;I forked the official AWS repository &lt;a href="https://github.com/awslabs/route53-dynamic-dns-with-lambda" rel="noopener noreferrer"&gt;awslabs/route53-dynamic-dns-with-lambda&lt;/a&gt; and created &lt;a href="https://github.com/revsystem/route53-dynamic-dns-with-lambda" rel="noopener noreferrer"&gt;revsystem/route53-dynamic-dns-with-lambda&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The fork includes the following changes, organized by branch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fix/lambda-security&lt;/strong&gt;: Fixed security vulnerabilities in the Lambda function (timing attack, input validation, exception handling, information leakage). Improved code quality (resolved function name conflicts, unified return types, moved boto3 to module level, removed Python 2 compatibility code).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fix/cdk-improvements&lt;/strong&gt;: Tightened IAM policy resource restrictions (Route 53, CloudWatch Logs). Changed DynamoDB RemovalPolicy to RETAIN and billing mode to PAY_PER_REQUEST. Set Lambda log retention period and reserved concurrency. Removed unused imports and improved CDK-Nag suppressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fix/client-scripts&lt;/strong&gt;: Fixed variable quoting, operator errors, deprecated command substitutions, and JSON construction safety in dyndns.sh. Added JSON injection prevention, improved exception handling, and secret masking in newrecord.py.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;test/add-tests&lt;/strong&gt;: Completely rewrote the unimplemented tests (all assertions were commented out). Added 7 CDK stack tests and 15 Lambda unit tests (22 total). Covers GET/SET normal cases, validation, authentication, and error handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;docs/fix-typos-and-docs&lt;/strong&gt;: Fixed typos in README.md and invocation.md. Updated response examples in invocation.md to match the Lambda implementation. Added version range specification to requirements.txt. Updated cdk.json feature flags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fix/cdk-nag-log-retention&lt;/strong&gt;: Added CDK-Nag suppressions for the custom resource Lambda auto-generated by the &lt;code&gt;log_retention&lt;/code&gt; setting. Since the CDK internal resource cannot be modified, the suppression includes an &lt;code&gt;applies_to&lt;/code&gt; clause with documented rationale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;feat/manage-record-script&lt;/strong&gt;: Created managerecord.py to allow viewing configuration, immediately changing TTL, and deleting records via subcommands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CDK-Nag is a tool that automatically checks CDK applications against security and compliance best practices. Error-level violations cause &lt;code&gt;cdk synth&lt;/code&gt; to fail, blocking deployment. Warning-level violations do not block deployment. To intentionally exempt a rule, you set a suppression with a documented reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the AWS Environment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A domain hosted in Route 53&lt;/li&gt;
&lt;li&gt;Permissions to operate Lambda, DynamoDB, and Route 53&lt;/li&gt;
&lt;li&gt;An environment with the AWS CLI available&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Architecture Overview
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Operator]
  |
  |-- newrecord.py (create/update) --&amp;gt; DynamoDB (hostname -&amp;gt; JSON)
  |
[EdgeRouter X]
  |-- dyndns.sh --&amp;gt; Lambda URL --&amp;gt; Lambda --&amp;gt; DynamoDB + Route 53
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploying the CDK Stack
&lt;/h3&gt;

&lt;p&gt;This solution is deployed using AWS CDK. The Lambda function, DynamoDB table, and IAM roles are created at once.&lt;/p&gt;

&lt;p&gt;Clone the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/revsystem/route53-dynamic-dns-with-lambda.git

&lt;span class="nb"&gt;cd &lt;/span&gt;route53-dynamic-dns-with-lambda
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the Python dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bootstrap is required the first time you deploy to a given account and region combination (environment). If the account is the same but the region differs, run it separately for each region.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If you are told the CDK CLI version is too old, update 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 &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; aws-cdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy the stack:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;When the deployment completes, the Lambda function URL is output. You will use this URL in the client configuration later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring the DNS Record
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;newrecord.py&lt;/code&gt; to interactively configure the Route 53 hosted zone and record set. The configuration is saved to the DynamoDB table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 newrecord.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your AWS profile or region differs from the defaults, specify the environment variables:&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="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-east-1 python3 newrecord.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter the hosted zone name (e.g., &lt;code&gt;example.jp&lt;/code&gt;), hostname (e.g., &lt;code&gt;router.example.jp&lt;/code&gt;), TTL (default 60), and shared secret in order. If the hosted zone does not exist, you will be asked whether to create it.&lt;/p&gt;

&lt;p&gt;The shared secret, together with the Lambda function URL, is the key to authentication. Set it to a sufficiently complex string. Using a random string generated by &lt;code&gt;openssl rand -hex 32&lt;/code&gt; is recommended.&lt;/p&gt;

&lt;p&gt;Once the input is complete, a confirmation is displayed:&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;##############################################&lt;/span&gt;
&lt;span class="c"&gt;#                                            #&lt;/span&gt;
&lt;span class="c"&gt;# The following configuration will be saved: #&lt;/span&gt;
&lt;span class="c"&gt;#                                            #&lt;/span&gt;
  Host name:  router.example.jp
  Hosted zone &lt;span class="nb"&gt;id&lt;/span&gt;: ZXXXXXXXXXXXXXXXXXXXXXXX
  Record &lt;span class="nb"&gt;set &lt;/span&gt;TTL: 60
  Secret: &lt;span class="k"&gt;********&lt;/span&gt;
&lt;span class="c"&gt;#                                            #&lt;/span&gt;
&lt;span class="c"&gt;#      do you want to continue? (y/n)        #&lt;/span&gt;
&lt;span class="c"&gt;#                                            #&lt;/span&gt;
&lt;span class="c"&gt;##############################################&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the setup is complete, the parameters needed to run &lt;code&gt;dyndns.sh&lt;/code&gt; are displayed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./dyndns.sh &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; https://XXXXXXXXXX.lambda-url.REGION.on.aws/ &lt;span class="nt"&gt;-h&lt;/span&gt; router.example.jp &lt;span class="nt"&gt;-s&lt;/span&gt; &amp;lt;YOUR_SECRET&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take note of the Lambda function URL and shared secret. They will be used in the EdgeRouter X configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;Test-run &lt;code&gt;dyndns.sh&lt;/code&gt; to verify the integration between the Lambda function and Route 53. On environments that use &lt;code&gt;shasum&lt;/code&gt;, you may need to install &lt;code&gt;perl-Digest-SHA&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;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;perl-Digest-SHA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the IP address retrieval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./dyndns.sh &lt;span class="nt"&gt;-m&lt;/span&gt; get &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"https://XXXXXXXXXX.lambda-url.REGION.on.aws/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a public IP address is returned, the Lambda function is working correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  DDNS Client Implementation Approach
&lt;/h2&gt;

&lt;p&gt;EdgeRouter X has a built-in DDNS feature that can be configured with &lt;code&gt;set service dns dynamic&lt;/code&gt;. Internally it runs ddclient and supports standard protocols such as dyndns2 and cloudflare.&lt;/p&gt;

&lt;p&gt;However, the AWS DDNS solution uses a protocol that sends an HTTP POST with JSON to a Lambda function URL and performs application-level authentication using a SHA-256 hash. The Lambda function URL itself is published with authentication type NONE (public access), and authentication is achieved by verifying the hash value included in the request within the Lambda function. Since ddclient does not have a definition for this protocol, the built-in DDNS feature cannot be used.&lt;/p&gt;

&lt;p&gt;Instead, we periodically execute the shell script &lt;code&gt;dyndns.sh&lt;/code&gt; provided in the repository using the EdgeRouter X task scheduler.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The following steps are executed on the EdgeRouter X CLI or via SSH.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Deploying dyndns.sh
&lt;/h3&gt;

&lt;p&gt;SSH into the EdgeRouter X and download the script. Files placed under &lt;code&gt;/config/scripts/&lt;/code&gt; are preserved after firmware upgrades.&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;-o&lt;/span&gt; /config/scripts/dyndns.sh https://raw.githubusercontent.com/revsystem/route53-dynamic-dns-with-lambda/master/dyndns.sh

&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /config/scripts/dyndns.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dyndns.sh&lt;/code&gt; internally uses &lt;code&gt;shasum -a 256&lt;/code&gt; to generate the authentication hash. Firmware 3.0.1 includes the &lt;code&gt;shasum&lt;/code&gt; command, so no script modification was needed. If &lt;code&gt;shasum&lt;/code&gt; is not found on older firmware, you can replace the relevant line with &lt;code&gt;openssl dgst -sha256&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying dyndns.sh
&lt;/h3&gt;

&lt;h4&gt;
  
  
  IP Address Retrieval Check
&lt;/h4&gt;

&lt;p&gt;First, use the &lt;code&gt;-m get&lt;/code&gt; mode to verify that the connection to the Lambda function URL and IP address retrieval succeed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/config/scripts/dyndns.sh &lt;span class="nt"&gt;-m&lt;/span&gt; get &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"https://XXXXXXXXXX.lambda-url.REGION.on.aws/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a global IP address is returned, the network connection and Lambda function are working correctly. If nothing is returned, check the Lambda function URL, DNS resolution, or firewall rules.&lt;/p&gt;

&lt;h4&gt;
  
  
  DNS Record Update Check
&lt;/h4&gt;

&lt;p&gt;Next, use the &lt;code&gt;-m set&lt;/code&gt; mode to actually update the Route 53 record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/config/scripts/dyndns.sh &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"https://XXXXXXXXXX.lambda-url.REGION.on.aws/"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="s2"&gt;"router.example.jp"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;YOUR_SECRET&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On success, a response like the following is returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"return_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"return_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"router.example.jp has been updated to XXX.XXX.XXX.XXX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"201"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the IP address has not changed, the following response is returned. This is also normal behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"return_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"return_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your IP address matches the current Route53 DNS record."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a Wrapper Script
&lt;/h3&gt;

&lt;p&gt;Since &lt;code&gt;dyndns.sh&lt;/code&gt; operates via command-line arguments, prepare a wrapper script for the task scheduler to call.&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;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;' &amp;gt; /config/scripts/run-ddns.sh
#!/bin/bash
/config/scripts/dyndns.sh &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -m set &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -u "https://XXXXXXXXXX.lambda-url.REGION.on.aws/" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -h "router.example.jp" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -s "&amp;lt;YOUR_SECRET&amp;gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  &amp;gt;&amp;gt; /var/log/aws-ddns.log 2&amp;gt;&amp;amp;1
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /config/scripts/run-ddns.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Registering with the Task Scheduler
&lt;/h3&gt;

&lt;p&gt;Configure the task scheduler in EdgeOS CLI configuration mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;configure
&lt;span class="nb"&gt;set &lt;/span&gt;system task-scheduler task aws-ddns interval 300m
&lt;span class="nb"&gt;set &lt;/span&gt;system task-scheduler task aws-ddns executable path /config/scripts/run-ddns.sh
commit
save
&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs &lt;code&gt;run-ddns.sh&lt;/code&gt; every 300 minutes (5 hours). Adjust the &lt;code&gt;interval&lt;/code&gt; value based on how frequently your ISP changes your IP address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying the Configuration
&lt;/h3&gt;

&lt;p&gt;Check the task scheduler registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;configure
show system task-scheduler
&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verifying the DNS Response
&lt;/h3&gt;

&lt;p&gt;Verify that the record has been correctly reflected in Route 53. There is one caveat here: the EdgeRouter X CLI is Vyatta-based and interprets &lt;code&gt;?&lt;/code&gt; as a help invocation key. This behavior persists even in operational mode (the &lt;code&gt;$&lt;/code&gt; prompt). Therefore, you cannot paste a URL containing &lt;code&gt;?&lt;/code&gt; directly into the CLI.&lt;/p&gt;

&lt;p&gt;To work around this, create and execute a script file with &lt;code&gt;vi&lt;/code&gt;. Using &lt;code&gt;echo&lt;/code&gt; does not help because the CLI still interprets the &lt;code&gt;?&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;vi /tmp/dnscheck.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write the following content and save:&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; &lt;span class="s2"&gt;"https://dns.google/resolve?name=router.example.jp&amp;amp;type=A"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Execute the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sh /tmp/dnscheck.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is working, JSON like the following is returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"TC"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"RD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"RA"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"CD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"router.example.jp."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"router.example.jp."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"TTL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"XXX.XXX.XXX.XXX"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Comment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Response from 205.251.196.248."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the IP address in the &lt;code&gt;data&lt;/code&gt; field under &lt;code&gt;Answer&lt;/code&gt; matches the WAN interface IP address, the DDNS setup is working correctly. You can check the WAN IP address with &lt;code&gt;show interfaces&lt;/code&gt;. The &lt;code&gt;205.251.196.248&lt;/code&gt; in the &lt;code&gt;Comment&lt;/code&gt; is a Route 53 nameserver IP address, indicating that the record is being served from the AWS side.&lt;/p&gt;

&lt;p&gt;If you want to use &lt;code&gt;dig&lt;/code&gt; or &lt;code&gt;nslookup&lt;/code&gt;, you can install the &lt;code&gt;dnsutils&lt;/code&gt; package, but the EdgeRouter X has limited storage (256MB NAND). For simple DNS verification, the method above is sufficient, and it is best to avoid installing unnecessary packages.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ping&lt;/code&gt; can also be used as a verification method that does not involve &lt;code&gt;?&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;ping router.example.jp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line shows &lt;code&gt;PING router.example.jp (XXX.XXX.XXX.XXX)&lt;/code&gt;, revealing the resolved IP address. When run from the LAN behind the router, the ping targets the router's own WAN IP address, so no reply is returned. Stop with Ctrl+C. Use this only to verify whether name resolution succeeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking the Logs
&lt;/h3&gt;

&lt;p&gt;The execution results of &lt;code&gt;run-ddns.sh&lt;/code&gt; are appended to &lt;code&gt;/var/log/aws-ddns.log&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;&lt;span class="nb"&gt;cat&lt;/span&gt; /var/log/aws-ddns.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Task scheduler execution may not be recorded in the system log. Verify scheduler operation by checking the log output in &lt;code&gt;/var/log/aws-ddns.log&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing DNS Records
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;managerecord.py&lt;/code&gt; to view, modify, or delete configurations set by &lt;code&gt;newrecord.py&lt;/code&gt;. The DynamoDB table name is automatically resolved from CloudFormation, so you do not need to look it up manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reconfiguring / Changing the Shared Secret
&lt;/h3&gt;

&lt;p&gt;Re-running &lt;code&gt;newrecord.py&lt;/code&gt; overwrites the configuration for the same hostname. Use this method to change the TTL or shared secret.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viewing the Current Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 managerecord.py show router.example.jp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Displays the configuration stored in DynamoDB along with the current IP and TTL from Route 53.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hostname        : router.example.jp
Hosted zone ID  : ZXXXXXXXXXXXXXXXXXXXXXXX
TTL             : 60
Secret          : ********
Current IP      : XXX.XXX.XXX.XXX
Route 53 TTL    : 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Immediately Applying a TTL Change
&lt;/h3&gt;

&lt;p&gt;The Lambda function only checks for IP address changes and does not compare TTL values. Therefore, the Route 53 TTL is not updated unless the IP address changes. To apply a TTL change immediately, use the &lt;code&gt;update-ttl&lt;/code&gt; subcommand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 managerecord.py update-ttl router.example.jp 300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This updates both the DynamoDB TTL and the Route 53 record simultaneously. From the next IP address change onward, the updated TTL is automatically applied.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deleting a Record
&lt;/h3&gt;

&lt;p&gt;To delete only the DynamoDB record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 managerecord.py delete router.example.jp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To also delete the Route 53 DNS record, add &lt;code&gt;--also-route53&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;python3 managerecord.py delete &lt;span class="nt"&gt;--also-route53&lt;/span&gt; router.example.jp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In both cases, a confirmation prompt is displayed before execution.&lt;/p&gt;

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

&lt;p&gt;I built a DDNS client that updates Route 53 DNS records from an EdgeRouter X using AWS Lambda + Route 53 + DynamoDB. This allows DDNS operation entirely within the AWS ecosystem, without depending on external DDNS services.&lt;/p&gt;

&lt;p&gt;Since this solution retrieves the IP address and calls the Lambda function URL using curl in a shell script, it can be adapted to other network devices or servers that support curl and a scheduler such as cron.&lt;/p&gt;

&lt;p&gt;The AWS repository does not provide a way to modify or delete configurations, so I created &lt;code&gt;managerecord.py&lt;/code&gt; to fill that gap. It supports viewing configurations, immediately changing TTL, and deleting records via subcommands.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>tutorial</category>
      <category>route53</category>
    </item>
  </channel>
</rss>
