<?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>Build and Deploy an Automatic Sync Solution for Amazon Bedrock Knowledge Bases</title>
      <dc:creator>Rev</dc:creator>
      <pubDate>Sat, 16 May 2026 14:42:27 +0000</pubDate>
      <link>https://dev.to/aws-builders/build-and-deploy-an-automatic-sync-solution-for-amazon-bedrock-knowledge-bases-4olf</link>
      <guid>https://dev.to/aws-builders/build-and-deploy-an-automatic-sync-solution-for-amazon-bedrock-knowledge-bases-4olf</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The AWS Blog post "&lt;a href="https://aws.amazon.com/blogs/machine-learning/build-and-deploy-an-automatic-sync-solution-for-amazon-bedrock-knowledge-bases/" rel="noopener noreferrer"&gt;Build and deploy an automatic sync solution for Amazon Bedrock Knowledge Bases&lt;/a&gt;" introduces a solution for automatically synchronizing documents with Amazon Bedrock Knowledge Bases.&lt;/p&gt;

&lt;p&gt;This AWS Blog post was published on April 27, 2026. During my validation in May 2026, I encountered two issues: S3 events routed via EventBridge were not being processed correctly, and the status was not being updated even after the Knowledge Base ingestion job completed.&lt;/p&gt;

&lt;p&gt;This article documents the fixes I applied to resolve these issues, the validation results, and operational considerations you should be aware of.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/build-and-deploy-an-automatic-sync-solution-for-amazon-bedrock-knowledge-bases/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/machine-learning/build-and-deploy-an-automatic-sync-solution-for-amazon-bedrock-knowledge-bases/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases" rel="noopener noreferrer"&gt;https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/revsystem/sample-automatic-sync-for-bedrock-knowledge-bases" rel="noopener noreferrer"&gt;https://github.com/revsystem/sample-automatic-sync-for-bedrock-knowledge-bases&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Overview of the Sample Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Architecture Overview from the AWS Blog
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Architecture Diagram
&lt;/h4&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%2F7xdrdbfazmbh09ybf60o.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%2F7xdrdbfazmbh09ybf60o.png" alt="Configuration diagram" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The original architecture diagram in the AWS Blog is low-resolution and hard to read when enlarged, so I recreated it above. The original diagram also lacked lines connecting the Event Processor Lambda and Sync Processor Lambda to DynamoDB, which I have added.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The sample implementation described in the AWS Blog executes the following workflow. As stated in the blog, this solution is designed with service quota limits in mind, and to improve resilience by enabling retries when those limits are exceeded.&lt;/p&gt;

&lt;h4&gt;
  
  
  Phase 1: Document Change Detection
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;User uploads/updates/deletes a document in Amazon S3&lt;/li&gt;
&lt;li&gt;Amazon EventBridge captures the S3 event and routes it to the Event Processor Lambda&lt;/li&gt;
&lt;li&gt;Event Processor Lambda determines the change type (create / update / delete) and records a change entry — including a &lt;code&gt;change_id&lt;/code&gt; — in Amazon DynamoDB (TRACKING_TABLE)&lt;/li&gt;
&lt;li&gt;Event Processor Lambda sends a change notification message to Amazon SQS&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Phase 2: Queuing and Rate Limiting
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Amazon SQS buffers the change notification messages and controls throughput to match the &lt;code&gt;StartIngestionJob&lt;/code&gt; quota (1 request per 10 seconds)&lt;/li&gt;
&lt;li&gt;Sync Processor Lambda receives SQS messages one at a time&lt;/li&gt;
&lt;li&gt;Creates job-tracking metadata in Amazon DynamoDB (METADATA_TABLE) and starts an AWS Step Functions workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Phase 3: Orchestration via Step Functions
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Check Quota Lambda verifies service quotas (5 concurrent per account / 1 concurrent per data source / 1 concurrent per Knowledge Base)&lt;/li&gt;
&lt;li&gt;If quotas are exceeded, the workflow waits 5 minutes and retries; otherwise it proceeds&lt;/li&gt;
&lt;li&gt;Start Sync Lambda calls the &lt;code&gt;StartIngestionJob&lt;/code&gt; API to kick off the sync job and records the job ID in metadata&lt;/li&gt;
&lt;li&gt;Monitor Sync Lambda periodically checks the status via &lt;code&gt;GetIngestionJob&lt;/code&gt; (waits 60 seconds and re-checks if the job is not yet complete)&lt;/li&gt;
&lt;li&gt;On completion, the workflow branches based on success or failure&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Phase 4: Knowledge Base Sync Processing
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Amazon Bedrock Knowledge Base ingests the entire data source&lt;/li&gt;
&lt;li&gt;Documents are converted to vector embeddings and stored in the vector store&lt;/li&gt;
&lt;li&gt;Data becomes available for semantic search&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Phase 5: Completion Processing, Notification, and Monitoring
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Monitor Sync Lambda detects job completion and updates the metadata status&lt;/li&gt;
&lt;li&gt;Sets the &lt;code&gt;ingestion_job_id&lt;/code&gt; on the corresponding change record in TRACKING_TABLE and marks it as processed&lt;/li&gt;
&lt;li&gt;Sends completion/failure notifications via Amazon SNS to email subscribers&lt;/li&gt;
&lt;li&gt;Visualizes metrics on an Amazon CloudWatch dashboard and detects anomalies via alarms&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Concrete Example: Uploading 50 Files in Bulk
&lt;/h4&gt;

&lt;p&gt;Following the &lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/README.md?plain=1#L233-L258" rel="noopener noreferrer"&gt;README.md example&lt;/a&gt;, the workflow proceeds as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;50 S3 events are generated → Event Processor records each change in DynamoDB and sends messages to SQS&lt;/li&gt;
&lt;li&gt;SQS delivers messages to Sync Processor at 10-second intervals&lt;/li&gt;
&lt;li&gt;Sync Processor starts Step Functions → if quotas allow, kicks off an ingestion job; one job ingests all 50 files (actually the entire data source) at once&lt;/li&gt;
&lt;li&gt;After completion, Monitor Sync Lambda updates DynamoDB and sends an SNS notification&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As also stated in the &lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/README.md?plain=1#L14-L16" rel="noopener noreferrer"&gt;README.md Important Note About Amazon Bedrock Knowledge Base Ingestion&lt;/a&gt;, the &lt;code&gt;StartIngestionJob&lt;/code&gt; operation scans the entire data source — not just the changed files. To ingest at the individual file level, a different API would need to be used. However, this design is intentional because the Knowledge Base's managed sync process determines whether each file is new, modified, or deleted and handles it appropriately.&lt;/p&gt;

&lt;p&gt;In this way, the solution is designed to track changes immediately while efficiently batching ingestion within service limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites and Constraints of the Sample Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Only One Data Source per Knowledge Base
&lt;/h3&gt;

&lt;p&gt;This implementation assumes that &lt;strong&gt;there is only one data source under the Knowledge Base&lt;/strong&gt;. If multiple data sources exist, the implementation uses the first entry returned by the &lt;code&gt;list_data_sources&lt;/code&gt; API, but since the order is not guaranteed, an unintended data source may be selected as the sync target. In the code below, &lt;code&gt;maxResults=10&lt;/code&gt; is set but &lt;code&gt;dataSourceSummaries[0]&lt;/code&gt; retrieves the first data source:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/sync_processor_lambda.py#L70-L77" rel="noopener noreferrer"&gt;https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/sync_processor_lambda.py#L70-L77&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sync Scope of the Bedrock Knowledge Base Data Source
&lt;/h3&gt;

&lt;p&gt;The scope that &lt;code&gt;StartIngestionJob&lt;/code&gt; actually scans and syncs to the Bedrock Knowledge Base is determined by the S3 URI configured in the Bedrock Knowledge Base data source settings — not by the &lt;code&gt;S3KeyPrefix&lt;/code&gt; parameter in &lt;code&gt;samconfig.toml&lt;/code&gt;. In the code below, &lt;code&gt;S3KeyPrefix&lt;/code&gt; is used only as a filter for the EventBridge rule. In other words, &lt;strong&gt;if the Bedrock Knowledge Base data source references the entire &lt;code&gt;s3://example-bucket/&lt;/code&gt;, even if you specify &lt;code&gt;documents/&lt;/code&gt; as the &lt;code&gt;S3KeyPrefix&lt;/code&gt;, any event triggered under &lt;code&gt;documents/&lt;/code&gt; will cause &lt;code&gt;StartIngestionJob&lt;/code&gt; to scan all documents under &lt;code&gt;s3://example-bucket/&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To avoid this, you need to align the S3 URI used when building the data source with the &lt;code&gt;S3KeyPrefix&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/samconfig.example.toml#L12" rel="noopener noreferrer"&gt;https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/samconfig.example.toml#L12&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/template.yaml#L234-L251" rel="noopener noreferrer"&gt;https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/template.yaml#L234-L251&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixes Applied to the Sample Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fix 1: S3 Events via EventBridge Not Being Processed Correctly
&lt;/h3&gt;

&lt;p&gt;This sample implementation uses an EventBridge rule as the trigger for the Event Processor Lambda. However, the &lt;code&gt;get_change_type()&lt;/code&gt; function is implemented to use direct S3 event notifications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/event_processor_lambda.py#L74-L91" rel="noopener noreferrer"&gt;https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/event_processor_lambda.py#L74-L91&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, the &lt;code&gt;event_name&lt;/code&gt; argument of this function references a field called &lt;code&gt;detail.name&lt;/code&gt;, but according to the &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html" rel="noopener noreferrer"&gt;AWS documentation&lt;/a&gt;, no such field exists in the EventBridge payload. The correct field to reference is &lt;code&gt;detail-type&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/event_processor_lambda.py#L119" rel="noopener noreferrer"&gt;https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/event_processor_lambda.py#L119&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To resolve these issues, I modified the payload reference and the &lt;code&gt;get_change_type()&lt;/code&gt; function. The specific changes are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/revsystem/sample-automatic-sync-for-bedrock-knowledge-bases/pull/3" rel="noopener noreferrer"&gt;https://github.com/revsystem/sample-automatic-sync-for-bedrock-knowledge-bases/pull/3&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix 2: Metadata in Sync Processor Lambda Not Being Updated Correctly
&lt;/h3&gt;

&lt;p&gt;The Event Processor Lambda generates a &lt;code&gt;change_id&lt;/code&gt; and stores it in the &lt;code&gt;autosync-tracking&lt;/code&gt; DynamoDB table. However, &lt;code&gt;change_id&lt;/code&gt; is not included in the SQS message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/event_processor_lambda.py#L184-L192" rel="noopener noreferrer"&gt;https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/event_processor_lambda.py#L184-L192&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Monitor Sync Status Lambda references the &lt;code&gt;change_ids&lt;/code&gt; field in the message only when the ingestion job reaches &lt;code&gt;COMPLETE&lt;/code&gt; status, and uses it to update the &lt;code&gt;autosync-tracking&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/monitor_sync_lambda.py#L93-L93" rel="noopener noreferrer"&gt;https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/monitor_sync_lambda.py#L93-L93&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Monitor Sync Status Lambda calls &lt;code&gt;mark_changes_as_processed()&lt;/code&gt; only when the ingestion job completes. This function references the &lt;code&gt;change_ids&lt;/code&gt; field to update the &lt;code&gt;processed&lt;/code&gt; column in the &lt;code&gt;autosync-tracking&lt;/code&gt; table. However, since &lt;code&gt;change_ids&lt;/code&gt; is not included in the SQS message, &lt;code&gt;processed&lt;/code&gt; always remains &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/monitor_sync_lambda.py#L151-L152" rel="noopener noreferrer"&gt;https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/monitor_sync_lambda.py#L151-L152&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To resolve this, I updated the implementation to include &lt;code&gt;change_id&lt;/code&gt; in the SQS message and modified Monitor Sync Status Lambda to handle both &lt;code&gt;change_id&lt;/code&gt; and &lt;code&gt;change_ids&lt;/code&gt; fields (since the original design intent was unclear, I made it compatible with both). The specific changes are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/revsystem/sample-automatic-sync-for-bedrock-knowledge-bases/pull/4" rel="noopener noreferrer"&gt;https://github.com/revsystem/sample-automatic-sync-for-bedrock-knowledge-bases/pull/4&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix 3: Correcting the Quota Value for Concurrent Ingestion Jobs per Account
&lt;/h3&gt;

&lt;p&gt;The README.md and the code state that the quota for &lt;code&gt;Concurrent ingestion jobs per account&lt;/code&gt; is 55, but the &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/service-limits.html" rel="noopener noreferrer"&gt;AWS official documentation&lt;/a&gt; shows the correct value is 5.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/check_quota_lambda.py#L26-L27" rel="noopener noreferrer"&gt;https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/src/check_quota_lambda.py#L26-L27&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/README.md?plain=1#L354-L356" rel="noopener noreferrer"&gt;https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/README.md?plain=1#L354-L356&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To resolve this, I set the &lt;code&gt;Concurrent ingestion jobs per account&lt;/code&gt; quota to 5. The specific changes are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/revsystem/sample-automatic-sync-for-bedrock-knowledge-bases/pull/5" rel="noopener noreferrer"&gt;https://github.com/revsystem/sample-automatic-sync-for-bedrock-knowledge-bases/pull/5&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The code used in this article is published on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/revsystem/sample-automatic-sync-for-bedrock-knowledge-bases" rel="noopener noreferrer"&gt;https://github.com/revsystem/sample-automatic-sync-for-bedrock-knowledge-bases&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All AWS resources in this guide are created in the &lt;code&gt;us-east-1&lt;/code&gt; region. If you use a different region, substitute the appropriate region name throughout.&lt;/p&gt;

&lt;p&gt;The S3 bucket and Bedrock Knowledge Base are created manually; all other resources are created using the AWS SAM CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the S3 Buckets
&lt;/h3&gt;

&lt;p&gt;You will create three S3 buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One for storing Bedrock Knowledge Base documents&lt;/li&gt;
&lt;li&gt;One for S3 server access logs&lt;/li&gt;
&lt;li&gt;One for storing text extracted from images in multimodal RAG (optional)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The S3 server access log bucket is not strictly required, but as noted in the &lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/22a51f8/README.md#amazon-s3-bucket-security-requirements" rel="noopener noreferrer"&gt;Amazon S3 Bucket Security Requirements section of the README&lt;/a&gt;, enabling S3 server access logging is recommended for auditing purposes.&lt;/p&gt;

&lt;p&gt;Below are the steps for creating the document storage bucket for Bedrock Knowledge Base.&lt;/p&gt;

&lt;h4&gt;
  
  
  S3 Bucket for Bedrock Knowledge Base Documents
&lt;/h4&gt;

&lt;p&gt;Create an S3 bucket for storing Bedrock Knowledge Base documents. Following the README, enable &lt;strong&gt;Block Public Access settings&lt;/strong&gt;, &lt;strong&gt;Default Encryption&lt;/strong&gt;, and &lt;strong&gt;Versioning&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&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;Bucket type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;General purpose&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bucket namespace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Account Regional namespace&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bucket name prefix&lt;/td&gt;
&lt;td&gt;&lt;code&gt;automatic-sync-for-bedrock-kb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block public access settings for this bucket&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Block all public access&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bucket Versioning&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Enable&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default Encryption&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Server-side encryption with Amazon S3-managed keys (SSE-S3)&lt;/code&gt;&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%2Fzx5w7wr7zviv8y5581zu.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%2Fzx5w7wr7zviv8y5581zu.png" alt="General configuration" width="800" height="500"&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%2F5dsgqe4v5uh7aupkmmvf.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%2F5dsgqe4v5uh7aupkmmvf.png" alt="Object Ownership" width="800" height="473"&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%2F4z54mhntq13ay6938ev2.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%2F4z54mhntq13ay6938ev2.png" alt="Bucket Versioning" width="800" height="127"&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%2F0eaaxovihuxzu5xk366q.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%2F0eaaxovihuxzu5xk366q.png" alt="Default Encryption" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating the bucket, open the &lt;strong&gt;Server access logging&lt;/strong&gt; block in the Properties tab and set it to &lt;code&gt;Enable&lt;/code&gt;. Specify the log bucket you created as the destination.&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%2F0eaaxovihuxzu5xk366q.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%2F0eaaxovihuxzu5xk366q.png" alt="Server access logging" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also in the Properties tab, open the &lt;strong&gt;Amazon EventBridge&lt;/strong&gt; block and set "Send notifications to Amazon EventBridge for all events in this bucket" to &lt;code&gt;On&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%2Fumbwj8wa2x44mmu26lkf.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%2Fumbwj8wa2x44mmu26lkf.png" alt="Amazon EventBridge" width="800" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, open the &lt;strong&gt;Bucket Policy&lt;/strong&gt; block in the Permissions tab and configure the bucket policy as shown below. Replace &lt;code&gt;Resource&lt;/code&gt; with the ARN of your Bedrock Knowledge Base document storage bucket.&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RestrictToTLSRequestsOnly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&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="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::automatic-sync-for-bedrock-kb-&amp;lt;YOUR_ACCOUNT_ID&amp;gt;-us-east-1-an"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::automatic-sync-for-bedrock-kb-&amp;lt;YOUR_ACCOUNT_ID&amp;gt;-us-east-1-an/*"&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;span class="nl"&gt;"Condition"&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="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Bool"&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="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"aws:SecureTransport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&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;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="w"&gt;
    &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="w"&gt;
&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuoz6n1pivxhhg9q8euem.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%2Fuoz6n1pivxhhg9q8euem.png" alt="Bucket Policy" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a folder named &lt;code&gt;documents&lt;/code&gt; inside this bucket for uploading documents.&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%2Fq7bykwfm8uhlqm5d8gc8.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%2Fq7bykwfm8uhlqm5d8gc8.png" alt="Documents folder" width="800" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Bedrock Knowledge Base
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The choices made here — such as Parsing strategy and Embedding model — are not essential to validating the automatic sync solution. As long as the S3 bucket is correctly configured in the data source, the rest can use default settings. For cost efficiency, I recommend selecting &lt;strong&gt;S3 Vectors&lt;/strong&gt; as the Vector store type.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Follow the Knowledge Base creation wizard and select &lt;strong&gt;Knowledge Base with vector store&lt;/strong&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%2Fq48vn306iyb5733t83xp.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%2Fq48vn306iyb5733t83xp.png" alt="Create Bedrock Knowledge Base" width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set the Knowledge Base name to &lt;code&gt;sample-automatic-sync-for-bedrock&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%2Fy1cpooztc5wlppcla29h.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%2Fy1cpooztc5wlppcla29h.png" alt="Knowledge Base Name" width="693" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;code&gt;Amazon S3&lt;/code&gt; as the data source type.&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%2Flvaj1k4e8gjcmw8o1jk4.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%2Flvaj1k4e8gjcmw8o1jk4.png" alt="Choose data source type" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set the data source name to &lt;code&gt;sample-automatic-sync-for-bedrock-data-source&lt;/code&gt;. For the S3 URI, specify the path to the document storage bucket you created earlier.&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%2F0m9idobj9l27w4bogboh.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%2F0m9idobj9l27w4bogboh.png" alt="Data source name" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;code&gt;Foundation models as a parser&lt;/code&gt; for the Parsing strategy to enable handling of PDFs and rich documents.&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%2Ffuy45d7ofe3jppfzedwt.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%2Ffuy45d7ofe3jppfzedwt.png" alt="Parsing strategy" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the parser model, select &lt;code&gt;Nova Pro 1.0&lt;/code&gt;. While the model selection screen shows various options, only On-demand profile models can be selected. Cross-region inference profiles and global inference profiles cannot be selected — attempting to use them will result in an error during Knowledge Base creation.&lt;/p&gt;

&lt;p&gt;For the Chunking strategy, select &lt;code&gt;Semantic chunking&lt;/code&gt;. Set Max tokens size for a chunk to &lt;code&gt;512&lt;/code&gt;. The maximum input token count for Cohere Embed Multilingual v3 is 512 tokens. If the chunk size exceeds this, text will be truncated during embedding, so Max tokens size for a chunk must be set to 512 or lower.&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%2Ftguzz35sihae2davl345.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%2Ftguzz35sihae2davl345.png" alt="Chunking strategy" width="800" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;code&gt;Cohere Embed Multilingual v3&lt;/code&gt; as the Embeddings model. This model is known for its strong multilingual embedding quality.&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%2Fi5ao50r7ggvztms6n2ai.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%2Fi5ao50r7ggvztms6n2ai.png" alt="Embeddings model" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Vector store, select &lt;code&gt;Quick create a new vector store&lt;/code&gt;, and for Vector store type, select &lt;code&gt;Amazon S3 Vectors&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%2Ftq445qff28b2oxqsfxr4.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%2Ftq445qff28b2oxqsfxr4.png" alt="Vector store" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Multimodal storage destination, specify the S3 bucket for storing text extracted from images in multimodal RAG. This is optional and can be left unconfigured.&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%2F68hpqkrw8w7as2jaua1h.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%2F68hpqkrw8w7as2jaua1h.png" alt="Multimodal storage destination" width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Clone the GitHub 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/sample-automatic-sync-for-bedrock-knowledge-bases.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to the repository root directory:&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;sample-automatic-sync-for-bedrock-knowledge-bases
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the sample configuration 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="nb"&gt;cp &lt;/span&gt;samconfig.example.toml samconfig.toml
&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;vi samconfig.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&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;KnowledgeBaseId&lt;/td&gt;
&lt;td&gt;The ID of your Bedrock Knowledge Base&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3BucketName&lt;/td&gt;
&lt;td&gt;The name of your document storage bucket (e.g. &lt;code&gt;automatic-sync-for-bedrock-kb-&amp;lt;YOUR_ACCOUNT_ID&amp;gt;-us-east-1-an&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3KeyPrefix&lt;/td&gt;
&lt;td&gt;The folder name inside the document storage bucket (e.g. &lt;code&gt;documents&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NotificationsEmail&lt;/td&gt;
&lt;td&gt;Email address for notifications (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LambdaMemorySize&lt;/td&gt;
&lt;td&gt;Lambda memory size (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LambdaTimeout&lt;/td&gt;
&lt;td&gt;Lambda timeout duration (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example samconfig.toml.&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;

&lt;span class="nn"&gt;[default.deploy.parameters]&lt;/span&gt;
&lt;span class="py"&gt;stack_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"autosync"&lt;/span&gt;
&lt;span class="py"&gt;resolve_s3&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;s3_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"autosync"&lt;/span&gt;
&lt;span class="py"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="py"&gt;confirm_changeset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;capabilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CAPABILITY_IAM"&lt;/span&gt;
&lt;span class="py"&gt;disable_rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;parameter_overrides&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"KnowledgeBaseId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;your-knowledge-base-id&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt; &lt;span class="py"&gt;S3BucketName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;your-s&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;-bucket-name&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt; &lt;span class="py"&gt;S3KeyPrefix&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;optional-prefix&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt; &lt;span class="py"&gt;NotificationsEmail&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;optional-email&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt; &lt;span class="py"&gt;LambdaMemorySize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt; &lt;span class="py"&gt;LambdaTimeout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="py"&gt;image_repositories&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;Run the deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build
sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt; &lt;span class="nt"&gt;--profile&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;YOUR_PROFILE&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;YOUR_REGION&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;code&gt;--guided&lt;/code&gt; option, the CLI will prompt you as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Setting default arguments &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'sam deploy'&lt;/span&gt;
&lt;span class="o"&gt;=========================================&lt;/span&gt;
Stack Name &lt;span class="o"&gt;[&lt;/span&gt;autosync]:
AWS Region &lt;span class="o"&gt;[&lt;/span&gt;us-east-1]:
Parameter KnowledgeBaseId &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;YOUR_KNOWLEDGE_BASE_ID&amp;gt;]:
Parameter S3BucketName &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;YOUR_S3_BUCKET_NAME&amp;gt;]:
Parameter S3KeyPrefix &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;YOUR_S3_KEY_PREFIX&amp;gt;]:
Parameter NotificationsEmail &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;YOUR_EMAIL_ADDRESS&amp;gt;]:
Parameter LambdaMemorySize &lt;span class="o"&gt;[&lt;/span&gt;256]:
Parameter LambdaTimeout &lt;span class="o"&gt;[&lt;/span&gt;60]:
&lt;span class="c"&gt;#Shows you resources changes to be deployed and require a 'Y' to initiate deploy&lt;/span&gt;
Confirm changes before deploy &lt;span class="o"&gt;[&lt;/span&gt;Y/n]: Y
&lt;span class="c"&gt;#SAM needs permission to be able to create roles to connect to the resources in your template&lt;/span&gt;
Allow SAM CLI IAM role creation &lt;span class="o"&gt;[&lt;/span&gt;Y/n]: Y
&lt;span class="c"&gt;#Preserves the state of previously provisioned resources when an operation fails&lt;/span&gt;
Disable rollback &lt;span class="o"&gt;[&lt;/span&gt;Y/n]: Y
Save arguments to configuration file &lt;span class="o"&gt;[&lt;/span&gt;Y/n]: Y
SAM configuration file &lt;span class="o"&gt;[&lt;/span&gt;samconfig.toml]:
SAM configuration environment &lt;span class="o"&gt;[&lt;/span&gt;default]:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful deployment will display the following message in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Successfully created/updated stack - autosync in us-east-1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more details on deploying and troubleshooting with the AWS SAM CLI, refer to &lt;a href="https://github.com/aws-samples/sample-automatic-sync-for-bedrock-knowledge-bases/blob/main/README-SAM.md" rel="noopener noreferrer"&gt;README-SAM.md&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  SNS Subscription Confirmation
&lt;/h3&gt;

&lt;p&gt;During deployment, an SNS subscription is created. This subscription is used to receive notifications when documents are uploaded or deleted.&lt;/p&gt;

&lt;p&gt;When the subscription is created, an email like the following will be sent to the &lt;code&gt;NotificationsEmail&lt;/code&gt; address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subject: AWS Notification - Subscription Confirmation

You have chosen to subscribe to the topic:
arn:aws:sns:us-east-1:&amp;lt;YOUR_ACCOUNT_ID&amp;gt;:autosync-notifications

To confirm this subscription, click or visit the link below (If this was in error no action is necessary):
Confirm subscription

Please do not reply directly to this email. If you wish to remove yourself from receiving all future SNS subscription confirmation requests please send an email to sns-opt-out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click &lt;strong&gt;Confirm subscription&lt;/strong&gt; to activate the SNS subscription. Be careful not to accidentally click the unsubscribe link.&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%2Fs70r9yhn6zparr4wgp3j.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%2Fs70r9yhn6zparr4wgp3j.png" alt="SNS Subscription Confirmation" width="553" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The subscription confirmation email may contain an unsubscribe link. Clicking this link immediately cancels the subscription. Corporate email security systems or spam filters may automatically open links in incoming emails to inspect them, potentially triggering the unsubscribe link unintentionally.&lt;/p&gt;

&lt;p&gt;To prevent this, you should confirm the SNS subscription via the AWS Management Console or AWS CLI, as described in this article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://repost.aws/knowledge-center/prevent-unsubscribe-all-sns-topic" rel="noopener noreferrer"&gt;https://repost.aws/knowledge-center/prevent-unsubscribe-all-sns-topic&lt;/a&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Uploading a Document
&lt;/h3&gt;

&lt;p&gt;Upload a document using the &lt;code&gt;aws s3 cp&lt;/code&gt; command or from the S3 Management Console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;YOUR_DOCUMENT_PATH&lt;span class="o"&gt;}&lt;/span&gt; s3://&lt;span class="o"&gt;{&lt;/span&gt;YOUR_S3_BUCKET_NAME&lt;span class="o"&gt;}&lt;/span&gt;/&lt;span class="o"&gt;{&lt;/span&gt;YOUR_S3_KEY_PREFIX&lt;span class="o"&gt;}&lt;/span&gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verifying Document Sync
&lt;/h3&gt;

&lt;p&gt;You can verify document sync from the Bedrock Knowledge Base dashboard. The sync history for the data source is shown in &lt;strong&gt;Sync history&lt;/strong&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%2Fipfqfhllymh3jgv9edva.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%2Fipfqfhllymh3jgv9edva.png" alt="Bedrock Knowledge Base Dashboard" width="800" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the SNS subscription is active, a message like the following will be sent when the ingestion job completes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subject: Bedrock KB Sync Job COMPLETE

{
  "knowledge_base_id": "&amp;lt;YOUR_KNOWLEDGE_BASE_ID&amp;gt;",
  "job_id": "&amp;lt;YOUR_JOB_ID&amp;gt;",
  "status": "COMPLETE",
  "statistics": {
    "numberOfDocumentsDeleted": 0,
    "numberOfDocumentsFailed": 0,
    "numberOfDocumentsScanned": 1,
    "numberOfMetadataDocumentsModified": 0,
    "numberOfMetadataDocumentsScanned": 0,
    "numberOfModifiedDocumentsIndexed": 0,
    "numberOfNewDocumentsIndexed": 1
  },
  "processed_changes": 1
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checking DynamoDB Data
&lt;/h3&gt;

&lt;p&gt;The DynamoDB table names are &lt;code&gt;autosync-tracking&lt;/code&gt; and &lt;code&gt;autosync-metadata&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;aws dynamodb scan &lt;span class="nt"&gt;--table-name&lt;/span&gt; autosync-tracking &lt;span class="nt"&gt;--profile&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;YOUR_PROFILE&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;YOUR_REGION&lt;span class="o"&gt;}&lt;/span&gt;
aws dynamodb scan &lt;span class="nt"&gt;--table-name&lt;/span&gt; autosync-metadata &lt;span class="nt"&gt;--profile&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;YOUR_PROFILE&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;YOUR_REGION&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output from the &lt;code&gt;autosync-tracking&lt;/code&gt; table. The &lt;code&gt;change_type&lt;/code&gt; field records deletions and new additions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;{
    "Items": [
        {
            "knowledge_base_id": {
&lt;/span&gt;&lt;span class="gp"&gt;                "S": "&amp;lt;YOUR_KNOWLEDGE_BASE_ID&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="go"&gt;            },
            "event_time": {
                "S": "2026-05-10T17:14:43Z"
            },
            "ingestion_job_id": {
                "S": "KGF5YGBN2P"
            },
            "processed": {
                "BOOL": true
            },
            "timestamp": {
                "N": "1778433285.169089"
            },
            "bucket": {
&lt;/span&gt;&lt;span class="gp"&gt;                "S": "&amp;lt;YOUR_S3_BUCKET_NAME&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="go"&gt;            },
            "change_type": {
                "S": "delete"
            },
            "key": {
                "S": "documents/n1120000.pdf"
            },
            "change_id": {
                "S": "4bc82a1e-56da-4f13-8d0e-a2db399aca8b"
            }
        },
        {
            "knowledge_base_id": {
&lt;/span&gt;&lt;span class="gp"&gt;                "S": "&amp;lt;YOUR_KNOWLEDGE_BASE_ID&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="go"&gt;            },
            "event_time": {
                "S": "2026-05-10T17:15:32Z"
            },
            "ingestion_job_id": {
                "S": "YGYDOSMKQR"
            },
            "processed": {
                "BOOL": true
            },
            "timestamp": {
                "N": "1778433333.72125"
            },
            "bucket": {
&lt;/span&gt;&lt;span class="gp"&gt;                "S": "&amp;lt;YOUR_S3_BUCKET_NAME&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="go"&gt;            },
            "change_type": {
                "S": "create"
            },
            "key": {
                "S": "documents/n1120000.pdf"
            },
            "change_id": {
                "S": "07b7b739-d44f-4a35-aa5c-8bc1fa104b01"
            }
        },
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;    ],
    "Count": 10,
    "ScannedCount": 10,
    "ConsumedCapacity": null
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Check logs in CloudWatch Logs. Logs are output to log groups matching &lt;code&gt;/aws/lambda/autosync-*&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%2Fqvec6c1ztos4f7p91ytd.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%2Fqvec6c1ztos4f7p91ytd.png" alt="CloudWatch Logs" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also check the status and metrics for each resource in the EventBridge, Step Functions, and Lambda Management Consoles.&lt;/p&gt;

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

&lt;p&gt;I validated the sample implementation introduced in the AWS Blog and confirmed its behavior end-to-end.&lt;/p&gt;

&lt;p&gt;In this sample implementation, when a file is uploaded, updated, or deleted in S3, the Knowledge Base sync API (&lt;code&gt;StartIngestionJob&lt;/code&gt;) is automatically invoked, ensuring reliable ingestion while respecting service quotas.&lt;br&gt;
The design — using EventBridge to detect S3 events, SQS to buffer and deliver messages, and Step Functions for orchestration — is highly efficient. Change tracking via DynamoDB also makes state management straightforward.&lt;/p&gt;

&lt;p&gt;While a few fixes were necessary due to discrepancies between the implementation and the specification, the sample implementation provided a clear picture of how automatic sync works end-to-end.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>amazonbedrock</category>
      <category>serverless</category>
    </item>
    <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/aws-builders/setting-up-an-l2tpipsec-remote-access-vpn-on-edgerouter-x-23e0</link>
      <guid>https://dev.to/aws-builders/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>
