VyOS from Scratch – Edition 1

VyOS from Scratch – Edition 1

As a VyOS evangelist, maintainer, and more, I’ve been meaning to write a simple series of “how-to” guides for VyOS for a while. Sure, there are plenty of guides out there, but I think a lot of them fall short in demonstrating WHY you are entering specific commands.

VyOS is capable, but it’s much more helpful if you can fully understand what it’s doing.

This initial guide will walk through the basic setup steps in replacing something like pfSense or some other consumer router with a simple and secure system running VyOS.

This is also going to be a very very long post, so buckle in!



Who is this For

Anybody that wants to use VyOS of course!

The nice thing about running VyOS is that if you set up it, and understand WHAT it’s doing, then you’ve actually learned something about networking.

Does something like pfSense work?

Sure, but it also doesn’t teach you much about what’s actually going on. And the knowledge learned in VyOS will easily translate to almost any other vendor like Cisco, Arista, and more. At that point, the concepts are the same, you are just learning the different dialects.


Environment

VyOS really doesn’t take much in resources.

8GB of storage and 2-4GB of RAM will be overkill for most basic setups.

CPU-wise, even small Pentium Silver CPUs can handle gigabit and beyond, even over VPN . Anything like an i3/i5 or a Xeon won’t break a sweat except in all but the most demanding scenarios.

If you virtualize, it can be helpful to set up a test environment. This could consist of a simple:

  • WAN Network. For testing this could be your existing LAN network.
  • A new “LAN” Network. For testing this could just be a separate port group or bridge, and it wouldn’t even need an uplink.
  • A simple VM with a GUI (or without), to run some testing.

If virtualization isn’t an option, VyOS can run on almost any device that is x86_64. I’ve run it on everything from:

  • NUCs with single NICs and USB/USB-C NICs.
  • WYSE 5070/3040 Thin Clients.
  • A variety of Dells, including R710s/R620s/R630s.
  • Anything Supermicro from 1U to 4U devices.

Finally, for this guide, I’ll be working with two NICs.

One is a WAN, connected to my current LAN. I’ll be creating a “network inside a network”. The second will be our new “LAN”. This is separate from my existing LAN so I can pretend like it’s a second private network. These interfaces can be anything:

  • Physical NICs.
  • VLANs.
  • Virtual NICs from ESXi, Proxmox, etc.

Install

The first step is to grab yourself a copy of the installer ISO.

Most people will want the Rolling Release. You can also build it yourself, via the Docker image, which is helpful if there are some extra packages you want to add.

If you want access to the LTS version, it requires self-building (an easy process), contributing, or a subscription. I highly recommend contributing, as even writing or cleaning up some documentation will get you access to the LTS prebuilt versions.

You can also support and get access via Patreon or BuyMeACoffee.


Note that for these guides, I will be using a recent version of rolling. This is important as some configuration nodes have changed locations and format between 1.3+ and the existing LTS versions (DHCP and IPv6 RAs are two things that come to mind).

Rolling Stability
It’s important to note that the rolling releases are generally completely stable. I would say 95% of them or more are functional without any major bugs.

Every once in a while, some broken functionality is pushed, but it is almost always fixed quickly. A broken upgrade is not a show-stopper and it is trivial to boot back into the working version with the built-in Image Management.

I have no issues running rolling releases in many production locations.

Install Steps

Installation is trivial:

  • Burn ISO to CD/DVD/USB stick, or boot ISO via remote management (iDrac, iLo, IPMI)
  • Login with vyos/vyos
  • Type install image
  • Answer the install steps
  • Reboot and remove installation media
Installation Gotchas
  • The installer ISO is a fully functional VyOS image. That means if you skip the install image step, you can configure the system, and it will work as intended… until you reboot. Don’t be me. Make sure to install if you want a permanent installation.
  • Some systems that lack a serial port get stuck on a black screen on the installer ISO. The simple fix is to edit the grub boot option line and remove the console=ttyS0,115200 option from the kernel boot arguments. Then just boot with CTRL-X as directed.

Basic Configuration

Once installed, you’ll be staring at the login screen. Login with vyos and the password you set up during install:


Decision Time!

Before you go any further, you need to make a decision. You need to pick out the subnet (or later subnets) where your network will live. Or, if you are converting over your existing network, just reuse it.

These should be RFC1918 addresses. You generally want a /24, which is 254 usable addresses.

So, there are three main groups of these private addresses you can choose from:

  • 10.X.Y.1-254, where X and Y are any number between 0 and 255. For the last number, 0 would be your “network” address, and 255 would be your broadcast address. You could then assign all your hosts to 1-254.
  • 172.X.Y.1-254, in this case X would be between 16 and 31, and Y could be 0 to 255.
  • 192.168.X.1-254, where X is between 0 and 255.
Subnetting Pro-tips

Many routers around the world off-the-shelf use a subnet like 10.0.0.0/24 or 192.168.0.0/24.

While you can use these subnets, future-you will hate yourself if you ever want to access your network remotely or access other networks when both sides are using the same subnet.

I would highly recommend:

  • Choose a subnet away from the norm. For example, 10.32.47.0/24.
  • If you want to plan ahead and are planning to break things out on VLANs, use a subnet like 10.32.X.0/24. That way, in the future, X can represent your VLAN number, and you can do a summary route like 10.32.0.0/16. I’ll be showing some examples of where this can be used later on in this post.

Configuring the LAN and Remote access

After the subnet is chosen, the first step is to get the router on the LAN and accessible via SSH. This makes it much easier to configure, as you are no longer tethered to a keyboard/monitor, VM console, can copy/paste, etc.

LAN IP


Goals:

  • Enter configure mode
  • Add address to the correct interface
  • Commit and save

The first step is to enter configure mode. This allows you to make changes to the system configuration. Type configure. VyOS also supports shortcuts and tab completion, so typing conf or conf<TAB> will do the same thing.

The prompt will change to include #, and the [edit] line will appear to designate that you are in configure mode.

Next, you need to add your LAN IP. You can check your interfaces by typing run show interfaces. This tells VyOS to show all the interfaces in operational mode (the mode you were in before you typed conf).

In my case, eth1 is attached to my LAN here. I’m going to use the subnet from above, 10.32.0.0/24. I prefer to use .1 for my routers, but you can use any IP in that range.

There are two methods to assign it once in conf mode. Either with the full path, or by drilling-down.

Full path:

configure
set interfaces ethernet eth1 address 10.32.0.1/24
set interfaces ethernet eth1 description LAN
commit
save

Drill down:

configure
edit interfaces ethernet eth1
set address 10.32.0.1/24
set description LAN
commit
save

Committing makes the configuration active, and saving saves it to disk so it will persist on a reboot.

Or:

You can show your work with show interfaces and run show interfaces

Set up DHCP

Now that that router has a LAN IP, it’s time to connect your laptop/desktop/etc up to it.

You can assign it a static IP in the chosen subnet (10.32.0.10/24 for example), but I imagine you are also going to want to assign a DHCP range for automatic configuration of clients.


Goals:

  • Enter configure mode
  • Set up DHCP ranges
  • commit and save

Let’s talk about the following config.

We are going to make two DHCP ranges, and leave some holes. This would allow us to assign the IP addresses between .2 and .49 for static or other uses, as well as .126-199 and .251-254.

While this is a non-standard usage, it demonstrates the capabilities. Many people would just have a single range.

set service dhcp-server shared-network-name LAN subnet 10.32.0.0/24 range 0 start 10.32.0.50
set service dhcp-server shared-network-name LAN subnet 10.32.0.0/24 range 0 stop 10.32.0.125
set service dhcp-server shared-network-name LAN subnet 10.32.0.0/24 range 1 start 10.32.0.200
set service dhcp-server shared-network-name LAN subnet 10.32.0.0/24 range 1 stop 10.32.0.250

Some other important config options follow.

You want to tell the DHCP server to hand out the router’s IP address as the default gateway and DNS. This will make it so (eventually), your LAN devices will have DNS and routing to the Internet:

set service dhcp-server shared-network-name LAN subnet 10.32.0.0/24 dns-server 10.32.0.1
set service dhcp-server shared-network-name LAN subnet 10.32.0.0/24 default-router 10.32.0.1
DNS Alert

This would also be the place to insert something like your pi-hole or active directory DNS server. In the above example, your pi-hole/ADDNS address or addresses would be where the dns-server option is.


SSH

The final step before we can access the router over SSH is to actually enable SSH:

set service ssh port 22
commit
save

At this point you ought to be able to hook up a LAN client, and verify that you have an IP, gateway, and DNS.

This is a fairly stock Debian VM I set up. As you can see, my IP is in one of my DHCP ranges, and my DNS and default route are the router’s IP.

This is on Linux, but the same thing could be confirmed on Windows or MacOS:

From there, you should be able to ssh into the router with the username vyos and password you set up during install:

And typing show configuration (in op mode), should show you the work done so far. VyOS also comes with a few defaults, like NTP servers configured.


NAT & DNS

NAT

The next important duty for a router and firewall is to be able to NAT. NAT allows all your private LAN devices to access the Internet.

Information Dump
Back when the Internet started, every device had a unique IP address.

Unfortunately, because of the numbering scheme used, there are only 4.2 billion of these address, at least in IPv4, and the transition to IPv6 is far from complete.

Because of this, every device on your network needs to be able to “pretend” that it is your router to access the Internet. Your router keeps track of which requests belong to which device.

Source NAT is what allows you to do this.

This is also where the above mentioned “summary route” can come into play. Instead of creating three separate NAT rules for 10.32.1.0/24, 10.32.2.0/24, and 10.32.3.0/24, you can create a single rule for 10.32.0.0/16.

For this setup, it’s important to identify which network interface will eventually be your WAN interface, even though it’s not configured yet.


Goals:

  • As before, enter configuration mode.
  • Set up a source NAT rule to target our LAN or LAN subnets.
  • Define the outgoing WAN interface on the rule.
  • Set the translation type of the rule to masquerade.

The type of NAT we will be using is called SOURCE NAT. This type of NAT is going to have three components:

  • Target all traffic from our LAN or LANs…
  • … when the traffic is going out to the Internet through this outgoing interface…
  • … Change the IP address from the LAN IP to the WAN IP

To do this:

set nat source rule 100 source address '10.32.0.0/24'
set nat source rule 100 outbound-interface 'eth0'
set nat source rule 100 translation address masquerade
commit
save

In the above example, the 100 is an arbitrary number (between 1-9999). The eth0 is my eventual WAN interface. masquerade translates the internal LAN IP to your public WAN IP.

Once we set up our WAN interface, we’ll be able to type show nat source rules in op mode, or run show nat source rules in conf mode to run the op mode command, it will be clear what’s happening.


DNS

The next step is to set up our VyOS instance as for DNS. This will allow you to use local DNS and caching instead of your ISP’s, but also eventually will allow you run custom DNS and hosts.


Goals:

  • Enter config mode
  • Set the DNS Listen Address
  • Set the subnets we are allowing from
  • Set the cache size to 0
  • commit and save

set service dns forwarding listen-address '10.32.0.1'
set service dns forwarding allow-from '10.32.0.0/24'
set service dns forwarding cache-size '0'
commit
save

As mentioned, this accomplishes three things.

  • It tells VyOS to listen for DNS requests on its LAN IP, 10.32.0.1.
  • It limits requests from your LAN subnet.
  • Finally, it sets the cache size to 0. This is good to start out with to make sure everything is working, but later you can bump it up to speed up multiple requests for the same sites.

Opportunity Alert
This is another opportunity to use the “summary” route that I’ve mentioned a few times.

Instead of allowing “10.32.0.0/24”, you would allow “10.32.0.0/16”. This would ensure that as you add VLANs and subnets, your DNS will just work without adding a long list of /24s.



DNS Forwarding

There is one more consideration to be made. As configured, your DNS server will run in “recursor” mode. This means it will take responsibility for fully resolving DNS on its own. This may be a bad thing for a number of reasons:

  • It leaks your IP to potential undesirables.
  • It’s almost always slower, as you don’t get to benefit from the caches of a large service like CloudFlare or Google.

It’s quite simple to set up in forwarding mode, meaning when you make a DNS request, you end up asking an upstream resolver.

In the following example, we’ll be using CloudFlare and Google:

set service dns forwarding name-server 1.1.1.1
set service dns forwarding name-server 1.0.0.1
set service dns forwarding name-server 8.8.8.8
set service dns forwarding name-server 8.8.4.4
commit
save

System DNS

One final step is to set the DNS for VyOS itself. Everything we’ve done so far has just set up VyOS to be a DNS server for your clients.

This will be the DNS that is used, for example, when you do a VyOS update, ping or traceroute from VyOS, etc.

The easiest thing to do if you’ve been following this guide, is just to use the DNS server you set up above:

set system name-server 10.32.0.1
commit
save

This is also another spot where if you wanted to use Google, CloudFlare, or AD-DNS, you could plug in that IP:

Alternatively, you call tell VyOS to use the DNS servers that it received from your WAN DHCP server with:

set system name-servers-dhcp eth0


Firewall

If you’ve made it this far, congrats! We are almost there!

This section will cover the creation of a zone-based firewall, which is far superior to the default method of attaching firewalls to interfaces.

The reason it is superior is simple. It’s a bit more hassle to set up, but it’s far easier to manage on an ongoing basis, plus, you start thinking about firewalling as flows of information from interface to interface.


Goals:

  • Enter configuration mode
  • Create a firewall to allow all LAN traffic.
  • Create firewall to allow all LAN traffic to access VyOS itself.
  • Create a firewall to protect the router itself from WAN.
  • Create a firewall to protect LAN devices from WAN.
  • Commit
  • Create LOCAL/LAN zones.
  • Assign appropriate interfaces to zones.
  • Commit and save

For as complex as firewalling seems, it’s actually pretty simple when you break it down.

A firewall tracks connection “states” to determine what is and is not allowed. This is where packets are originating and going to, ports involved, types of traffic like TCP/UDP/ICMP, and more.

We are going to build a very simple firewall that assumes that we want to block everything externally, and allow everything from LAN.

So let’s dig right into it.


LAN

First, create a firewall that will allow our LAN to access everything. This is a setup that would mimic what most consumer routers do:

conf
set firewall name LAN-WAN default-action accept
set firewall name LAN-LOCAL default-action accept
commit
save

The LAN-WAN and the LAN-LOCAL can be arbitrarily named anything. I use this naming scheme because as mentioned above, it’s better to think about firewalls as controlling the flow of information through the router, and these names are self-documenting.

LOCAL is a specific VyOS designation that means “this router”. When you are attaching the firewall in the zones, LOCAL will be what you use to control traffic destined to the firewall itself.

As should be obvious, we are just allowing everything. But you can set whatever rules you wanted. LAN-WAN would generally remain pretty open as it would be uncommon to block your own access to the Internet, but LAN-LOCAL might contain rules to maybe allow only specific devices access to manage the VyOS router.

Also note that we are just creating the firewalls here. They don’t become active until you attach them to the zones.


LOCAL

As mentioned, the LOCAL zone is traffic destined for the VyOS router itself.

In most cases, it will have a similar ruleset to the LAN one above, as most people want their router to have full access to the Internet and their LAN devices:

conf
set firewall name LOCAL-WAN default-action accept
set firewall name LOCAL-LAN default-action accept
commit
save
Food for Thought

While I might be getting a little ahead of myself here, it’s important to note that you could use the same firewall instead of creating four different firewalls as we’ve done here. The firewalls are just attached to interfaces or zones by names, so there’s nothing really stopping you from creating a single firewall and attaching it to four different places.

As a bit of a pro-tip though, I would recommend not doing this. Eventually you’ll want them broken out, and it’s far more of a hassle to do it later than to just do it during initial setup.


WAN

The WAN zone is where we are actually going to do most of our work.

The basic goal here will be two things. To block all access to the router itself, and to provide basic setup and template for future things like port forwarding to our LAN.

When you start moving data to and from the Internet, that’s when you need to start worrying about the state tracking I mentioned before.

For both LOCAL and LAN traffic, we’ll be setting up what’s known as a “stateful firewall”.

In stateful firewalling, there are three main states to worry about:

  • New – Traffic in a NEW state is the first packet from a destination to a source. Allowing or denying this packet ultimately determines whether the traffic will be allowed.
  • Established – Once traffic from NEW has been allowed, the subsequent packets are marked as ESTABLISHED. This is essentially traffic that has already been allowed by other rules. This is another basic requirement for a working stateful firewall.
  • Related – This means that this is traffic that is somehow related to already allowed traffic. Also required.
A note about rule numbers
As with the NAT rule numbers, the rule numbers you use for firewall are arbitrary.

With that said, it doesn’t mean you shouldn’t plan ahead a bit:

  • Rules are processed numerically, so for fastest firewalling and routing, you definitely want your ESTABLISHED/RELATED rule at the top. That ensures that existing traffic, which is going to be the bulk of the traffic your firewall processes, only has to look at a single rule.
  • Leave yourself some gaps for future rules. You’ll appreciate it later if you want to insert a new rule before an existing one, though you can always rename and move around.

WAN-LOCAL

As mentioned, the WAN-LOCAL firewall is traffic destined for the VyOS router itself. In the future, this will be where you allow traffic, say to your WireGuard port for VPN.

The first rule we want to build is to allow all ESTABLISHED and RELATED traffic. So we’ll:

  • Create the WAN-LOCAL firewall.
  • Set the default policy on the firewall to drop everything.
  • Create a rule to accept specific traffic for rule.
  • Set the match for the rule to our established and related states.
  • Set a description for ease of use later.

set firewall name WAN-LOCAL default-action drop
set firewall name WAN-LOCAL rule 5 action accept
set firewall name WAN-LOCAL rule 5 state established enable
set firewall name WAN-LOCAL rule 5 state related enable
set firewall name WAN-LOCAL rule 5 description "Allow EST/Related Traffic"
commit
save

The next rule we want on is to allow ICMP. Many people like to block this, but not me. I’ll defer to ShouldIBlockICMP.com on this.

  • Create a new rule matching ICMP
  • Match the state of NEW. This is what actually matches the unknown traffic to allow
  • Allow the traffic
  • Commit and save
set firewall name WAN-LOCAL rule 20 protocol icmp
set firewall name WAN-LOCAL rule 20 state new enable
set firewall name WAN-LOCAL rule 20 action accept
commit
save

And you can see that the firewall is created (but still not attached to any interfaces), by doing the op mode command show interfaces.

WAN-LAN

For now, the WAN-LAN firewall is identical to the WAN-LOCAL. In the future, this is where you will allow your port forward rules, or other traffic you want to send to your LAN devices, so it’s helpful to break it out beforehand.

As before, we create the firewall, set it to drop by default, allowed EST/RELATED, and allow ICMP, which won’t do anything now, but could be helpful in the future:

set firewall name WAN-LAN default-action drop
set firewall name WAN-LAN rule 5 action accept
set firewall name WAN-LAN rule 5 state established enable
set firewall name WAN-LAN rule 5 state related enable
set firewall name WAN-LAN rule 5 description "Allow EST/Related Traffic"
set firewall name WAN-LAN rule 20 protocol icmp
set firewall name WAN-LAN rule 20 state new enable
set firewall name WAN-LAN rule 20 action accept

Don’t forget you can dive down into levels as in the following example:


Finally! We have our firewalls set up and ready to deploy.


WAN and Zones

If you’ve been paying attention, we still don’t have one VERY important piece to this puzzle. We still don’t have WAN access!

Of course I did this to protect your router during the initial setup phase. Until you have firewalls ready to deploy, you probably don’t want to be kicking your brand new VyOS install out on the open Internet.


Zones

Zones are easily one of my favorite parts of VyOS, and something that in my opinion, puts it lightyears ahead of other firewall solutions.

Especially as your firewalling needs grow, zones just make it so EASY. As I sort of touched on, zones are a bit more work initially, but save you MUCH more time in the future.

Lockout Alert
Be careful and pay attention here.

If you mistype something and are still configuring VyOS over SSH, you can potentially lock yourself out.

The naming scheme as I’ve outlined above should be helpful here when creating our firewall zones. It basically says FROMZONE->TOZONE

So let’s run through the whole thing.

Goals:

  • Set the default action for the zone. This should always be drop to cover yourself in case you miss something.
  • Apply the “For traffic from X zone to current zone, attach Y firewall”
  • Add the interfaces that are part of this zone
  • Repeat for all zones
  • commit
  • save

So what does this look like for the LAN:

  • In this example we are working on the LAN zone
  • We drop everything by default
  • We assign the WAN-LAN firewall to traffic from any interface in the below WAN zone
  • Similarly for the LOCAL-LAN. This is traffic from the VyOS instance itself to LAN hosts.
  • We put eth1 in our LAN zone.
set zone-policy zone LAN default-action drop
set zone-policy zone LAN from WAN firewall name WAN-LAN
set zone-policy zone LAN from LOCAL firewall name LOCAL-LAN
set zone-policy zone LAN interface eth1



Repeat all steps for the LOCAL zone. The major difference here is the local-zone designation. As I’ve mentioned a few times, LOCAL is a special designation that means “this firewall”, so you don’t attach interfaces:

set zone-policy zone LOCAL local-zone
set zone-policy zone LOCAL from LAN firewall name LAN-LOCAL
set zone-policy zone LOCAL from WAN firewall name WAN-LOCAL
set zone-policy zone LOCAL default-action drop

Finally, the WAN zone is basically the same as the LAN zone. The only major change you should notice is that I just changed the firewall names and interface name:

set zone-policy zone WAN from LAN firewall name LAN-WAN
set zone-policy zone WAN from LOCAL firewall name LOCAL-WAN
set zone-policy zone WAN interface eth0
set zone-policy zone WAN default-action drop

If you were like me, you might have tried committing between steps. Unfortunately, if you don’t set up everything at once, you’ll quickly discover that zones are interdependent on one another. This happens because I’ve created the LAN zone, but the referenced WAN and LOCAL zones don’t exist yet.


Show your work

Hopefully, if you’ve done everything correctly, you should see all your zone policies set up under the op mode command run show zone-policy. This view should make it obvious which traffic is flowing through what firewall:


WAN Setup

Finally, here we are.

Setting up your WAN. If you’ve made it this far, congrats. You are basically 1 or 2 commands away from having a working router and firewall!

There are multiple ways to get a WAN address. For simplicity’s sake, I’ll just cover two of them here, static and DHCP assignment. For something like PPPoE, you’ll need a few more steps.

DHCP Assignment

DHCP is when your router asks your ISP’s servers for what it should use for its IP address and routing information. This is accomplished with one simple command (well, four if you add a description to the interface, and commit and save):

set interfaces ethernet eth0 address dhcp
set interfaces ethernet eth0 description WAN
commit
save

You can verify that the setup is complete with the op mode commands run show interfaces and run show ip route. (I have some debug variables set, so ignore the extra output:

In the above output, 10.21.21.57 is the IP address I was assigned via DHCP. The show ip route verifies that I have a default route.

Information Dump
Your “gateway” or default route, is the router your devices ask when they encounter an IP address that they don’t know how to get to.

In the networking world, this is represented by 0.0.0.0/0, which is a shortcut for the entire Internet

So in the above example, this VyOS knows how to talk directly to anything in the 10.21.21.0/24 or 10.32.0.0/24 subnets because they are directly connected. Anything outside of that, it needs to ask 10.21.21.1 for a route to it.

Static IP

Static IPs just skip the automatic configuration. It’s up to your ISP how this is configured, but usually it’s an extra step, manually applying your default route.

So to duplicate my above DHCP configuration:

set interfaces ethernet eth0 address 10.21.21.57/24
set protocols static route 0.0.0.0/0 next-hop 10.21.21.1
commit
save

As you can see in the following screenshot, I forgot the exact format, so I hit my <TAB> button to get hints:

Check your work

Assuming nothing has gone wrong, you should be able to verify everything with the op mode command ping. So let’s hit Google with 4 pings:

run ping www.google.com count 4

If all goes well, you should see a response:


Conclusion

So wow, this post turned into a bit of a novel. Hopefully if you’ve made it this far, your LAN clients now have accessible Internet. From a client, you should be able to ping google:


Obviously there are a lot of basic topics I haven’t covered yet. But I wanted to lay out and explain the basic setup first.

Future editions will feature:

  • Port Forwarding.
  • Hairpin NAT (this goes hand-in-hand with the first).
  • VPNs, especially WireGuard
  • How routing works when you have VPNs.
  • Some potential hardening tactics.
  • Other advanced topics like BGP/OSPF.

Cheers!