A service on Windows Server can be listening on its port and still look dead from every other machine. The built-in firewall drops unsolicited inbound traffic on all three profiles until a rule allows it, and that catches out almost everyone who deploys a new app. The quickest way to open a port in Windows Server Firewall is one line of PowerShell from an elevated prompt:
New-NetFirewallRule -DisplayName "Allow TCP 8080" -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow
The rule takes effect the moment the cmdlet returns, with no service restart. The rest of this guide does the same job in the Windows Defender Firewall console (wf.msc) and with netsh advfirewall. It then shows you how to check if the port is open from a second machine with Test-NetConnection, and how to list and remove rules without leaving duplicates behind. I ran through every command on a Windows Server 2025 Datacenter VM in my homelab in October 2026, with a Windows 11 box as the client, and it works end to end.
Before you start
Opening a port only helps when something is listening on it, and you can only prove the rule works from a second machine. That is why the lab had two VMs, and why you need the same:
- Windows Server 2025, 2022 or 2019
- An elevated PowerShell or Command Prompt
- A service listening on the port
- A second machine on the same network
In our lab the server is 10.0.1.50 and the Windows 11 client is 10.0.1.51. The New-NetFirewallRule family of cmdlets lives in the NetSecurity module, which has shipped with every Windows Server release since 2012, so nothing needs installing on 2019, 2022 or 2025.
Before adding anything, it is worth looking at what the firewall is doing right now. The profile list tells you whether the firewall is on and which default it applies.
Get-NetFirewallProfile | Format-Table Name, Enabled, DefaultInboundAction, DefaultOutboundAction
On a fresh Server 2025 install all three profiles are enabled, and the defaults read NotConfigured:
Name Enabled DefaultInboundAction DefaultOutboundAction
---- ------- -------------------- ---------------------
Domain True NotConfigured NotConfigured
Private True NotConfigured NotConfigured
Public True NotConfigured NotConfigured
Do not read NotConfigured as “nothing is blocked”. It means no policy overrides the built-in default, and that default blocks inbound. The older netsh view states it plainly, and it also tells you which profile is live, which matters later when you scope a rule.
netsh advfirewall show currentprofile
Our lab NIC sat on the Public profile, with inbound blocked and outbound allowed.
Public Profile Settings:
----------------------------------------------------------------------
State ON
Firewall Policy BlockInbound,AllowOutbound
LocalFirewallRules N/A (GPO-store only)
LocalConSecRules N/A (GPO-store only)
InboundUserNotification Disable
RemoteManagement Disable
UnicastResponseToMulticast Enable
A server that has not joined a domain usually lands on Public, because Windows treats an unknown network as untrusted. Keep that in mind: a rule limited to the Domain profile does nothing on this box. Before the rule existed, a plain TCP connect from the client to port 8080 (the .NET check from Step 4) gave up after 4 seconds, and ping to the server returned Request timed out., so both TCP and ICMP were being dropped.
Step 1: Open a port with New-NetFirewallRule
New-NetFirewallRule is the command you want for anything scripted, remote or repeatable. We start with the same one-liner from the top of the page, because it is the one you will reuse most.
New-NetFirewallRule -DisplayName "Allow TCP 8080" -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow
PowerShell prints the new rule object straight away:
Name : {14bb724b-8a8d-4592-8063-3324df091d49}
DisplayName : Allow TCP 8080
Description :
DisplayGroup :
Group :
Enabled : True
Profile : Any
Platform : {}
Direction : Inbound
Action : Allow
EdgeTraversalPolicy : Block
LooseSourceMapping : False
LocalOnlyMapping : False
Owner :
PrimaryStatus : OK
Status : The rule was parsed successfully from the store. (65536)
EnforcementStatus : NotApplicable
PolicyStoreSource : PersistentStore
PolicyStoreSourceType : Local
RemoteDynamicKeywordAddresses : {}
PolicyAppId :
PackageFamilyName :
Three fields deserve a look. Profile : Any means the rule applies on Domain, Private and Public, which is what you want unless you have a reason to narrow it. PolicyStoreSource : PersistentStore means it survives a reboot. And Name is a random GUID because we did not give one, which comes back to bite us in Step 5. The port itself does not appear in this output, because Windows keeps ports in a separate filter object. You read it with the port filter cmdlet.
Get-NetFirewallRule -DisplayName "Allow TCP 8080" | Get-NetFirewallPortFilter
That shows the protocol and the local port bound to the rule:
Protocol : TCP
LocalPort : 8080
RemotePort : Any
IcmpType : Any
DynamicTarget : Any
If LocalPort here does not match the port your service uses, the rule exists but will never match its traffic, so check this field before you suspect anything else.
UDP, several ports and port ranges
The same cmdlet handles UDP and multiple ports, so there is no need for one rule per port. Change -Protocol for UDP services such as syslog.
New-NetFirewallRule -DisplayName "Allow UDP 514 Syslog" -Direction Inbound -Protocol UDP -LocalPort 514 -Action Allow
For a web server that needs several ports, pass a comma list to -LocalPort. Piping the result to the port filter confirms all three landed in one rule:
New-NetFirewallRule -DisplayName "Allow Web Ports" -Direction Inbound -Protocol TCP -LocalPort 80,443,8000 -Action Allow | Get-NetFirewallPortFilter
The LocalPort field comes back as a list:
Protocol : TCP
LocalPort : {80, 443, 8000}
A contiguous range uses a hyphen instead, which is handy for apps that hand out ports from a pool.
New-NetFirewallRule -DisplayName "Allow TCP 5000-5010" -Direction Inbound -Protocol TCP -LocalPort 5000-5010 -Action Allow | Get-NetFirewallPortFilter
Windows stores the range as a single string, LocalPort : 5000-5010. One mistake we made on purpose: leave out -Protocol and the cmdlet refuses, because a port number means nothing without TCP or UDP. The error is not very clear about that:
New-NetFirewallRule : The protocol-specific options do not match the protocol that was chosen.
+ CategoryInfo : InvalidArgument: (MSFT_NetFirewallRule:root/standardcimv2/MSFT_NetFirewallRule) [New-NetFirewallRule], CimException
+ FullyQualifiedErrorId : HRESULT 0x80070057,New-NetFirewallRule
If you see “protocol-specific options do not match”, add -Protocol TCP or -Protocol UDP and run it again. Nothing is created when the cmdlet fails, so there is no half-made rule to clean up afterwards.
Give the rule a Name so a rerun cannot duplicate it
Windows does not treat -DisplayName as unique. Run the one-liner twice and you get two identical rules with different GUIDs, and Windows says nothing about it. The internal -Name property is unique, so set it yourself in any script you might run more than once:
New-NetFirewallRule -Name "app-tcp-8080" -DisplayName "Allow TCP 8080" -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow
A second run of that exact command now fails instead of stacking a copy, which is the behaviour you want from automation:
New-NetFirewallRule : Cannot create a file when that file already exists.
+ CategoryInfo : ResourceExists: (MSFT_NetFirewallRule:root/standardcimv2/MSFT_NetFirewallRule) [New-NetFirewallRule], CimException
+ FullyQualifiedErrorId : Windows System Error 183,New-NetFirewallRule
The message talks about a file, which is confusing, but error 183 is simply “already exists”. In a script, wrap the call in try/catch or check with Get-NetFirewallRule -Name app-tcp-8080 -ErrorAction SilentlyContinue first.
Step 2: Open a port in the Windows Defender Firewall console
The console is the better choice when you are on the server desktop and want to see every other rule while you work. Open it with Win + R, type wf.msc and press Enter, or go through Server Manager, Tools, Windows Defender Firewall with Advanced Security. We used the wizard to open TCP 8443 for a second service:

- 1Inbound Rules
- 2New Rule...
- Click Inbound Rules in the left pane.
- Click New Rule… in the Actions pane on the right.
Notice that the custom rules from Step 1 sit at the top of the list, mixed in with hundreds of built-in ones. A fresh Server 2025 install carried 316 rules in our lab, 77 of them enabled inbound, so a clear naming habit pays off quickly. The wizard opens on Rule Type, with Program selected by default:

- 1Port
- 2Next >
- Select Port.
- Click Next >.
The Protocol and Ports page is where the actual port goes. The box accepts the same syntax as PowerShell, so a comma list or a range works here too:

- 1TCP
- 2Specific local ports
- 3Next >
- Leave TCP selected, or pick UDP for a UDP service.
- Type
8443in the Specific local ports box. - Click Next >.
The Action page already has the right answer selected. The middle option only lets traffic in when it is protected by IPsec, which needs connection security rules that most homelab and small office servers do not have:

- 1Allow the connection
- 2Next >
- Keep Allow the connection.
- Click Next >.
The Profile page decides on which networks the rule applies. All three boxes are ticked by default, which matches Profile : Any from PowerShell:

- 1Domain, Private and Public
- 2Next >
- Leave Domain, Private and Public ticked unless you know which profile the NIC uses.
- Click Next >.
Clearing Public is a tempting “security” move, but our server was on Public, so a rule without it would never have matched. Check netsh advfirewall show currentprofile before you narrow this page. The last page names the rule:

- 1Name
- 2Finish
- Type a clear rule Name, such as
Allow TCP 8443 GUI. - Click Finish.
What you type in that box becomes the DisplayName, which is what Get-NetFirewallRule -DisplayName and netsh ... name= match on later, so put the port and the service in it. The wizard has no field for the unique -Name, so a GUI rule always gets a GUID. Back in PowerShell, the port filter confirms what the wizard saved:
Get-NetFirewallRule -DisplayName "Allow TCP 8443 GUI" | Get-NetFirewallPortFilter
The wizard rule carries the same TCP 8443 binding a cmdlet would have created:
Protocol : TCP
LocalPort : 8443
Its Name came back as {DCC2D1E7-E680-4BA5-9396-4E7A7B4DDADF} with Profile : Any, and the .NET check from Step 4 connected to 8443 from the client straight after Finish.
Step 3: Open a port with netsh advfirewall
Is netsh still worth knowing on Server 2025? Yes, if you work from cmd.exe, a batch file or an old deployment script, because netsh advfirewall firewall still works and writes to the same rule store. Microsoft’s own firewall command-line guide shows both side by side. We put the netsh version of the one-liner on port 9443, so it does not clash with the PowerShell rule.
netsh advfirewall firewall add rule name="Allow TCP 9443" dir=in action=allow protocol=TCP localport=9443
netsh answers with a single Ok. and nothing else. To see what it actually created, ask for the rule by name.
netsh advfirewall firewall show rule name="Allow TCP 9443"
The rule covers all three profiles and every remote address, the same defaults as PowerShell:
Rule Name: Allow TCP 9443
----------------------------------------------------------------------
Enabled: Yes
Direction: In
Profiles: Domain,Private,Public
Grouping:
LocalIP: Any
RemoteIP: Any
Protocol: TCP
LocalPort: 9443
RemotePort: Any
Edge traversal: No
Action: Allow
Ok.
UDP works the same way with protocol=UDP, which is how we opened SNMP on 161:
netsh advfirewall firewall add rule name="Allow UDP 161 SNMP" dir=in action=allow protocol=UDP localport=161
netsh has the same duplicate problem as -DisplayName. We ran the 9443 command a second time and got another Ok., not an error, so you only find out when you delete, which we cover in Step 5.
Step 4: Check that the port is open from another machine
A rule that exists is not the same as a port that answers. Check the server side first: if nothing is listening, no firewall rule will make the port reachable. Get-NetTCPConnection shows the listener and the process that owns it:
Get-NetTCPConnection -LocalPort 8080 -State Listen | Format-Table LocalAddress, LocalPort, State, OwningProcess
Our test listener was bound to every address on 8080:
LocalAddress LocalPort State OwningProcess
------------ --------- ----- -------------
0.0.0.0 8080 Listen 4252
0.0.0.0 means the service accepts connections on any interface. If you see 127.0.0.1 there instead, the app only listens on loopback, so no firewall rule will help and the change belongs in the app’s own config. The old netstat -ano piped to findstr :8080 gives the same answer if you prefer it. Once the listener checks out, test from the client, where Test-NetConnection runs a ping and a TCP handshake in one command. In our lab it stalled while the server dropped ping, so add the ICMP rule from the next subsection first if you want this test to come back quickly.
Test-NetConnection -ComputerName 10.0.1.50 -Port 8080
The line that matters is TcpTestSucceeded:
ComputerName : 10.0.1.50
RemoteAddress : 10.0.1.50
RemotePort : 8080
InterfaceAlias : Ethernet
SourceAddress : 10.0.1.51
TcpTestSucceeded : True
True means the handshake completed, so the rule and the listener both work. False points at a rule that does not match (wrong port, protocol or profile), at a service that is not listening, or at a device between the two hosts that drops the traffic. Our run took 48 seconds on a freshly booted client, so give it time before you assume it hung. It only returned once the server allowed ping, which is the first thing in the next section.
Allow ping (Test-NetConnection stalled without it in our lab)
Windows Server blocks inbound ICMP echo by default, so ping times out even when the port is open. Normally Test-NetConnection just warns that the ping failed and carries on with the TCP test, but in our lab Test-NetConnection against a server that dropped ping produced no output for more than five minutes before we gave up on it, even after the 8080 rule was in place. Allowing echo requests fixed that.
New-NetFirewallRule -DisplayName "Allow ICMPv4 Echo" -Direction Inbound -Protocol ICMPv4 -IcmpType 8 -Action Allow
After that rule, ping 10.0.1.50 from the client answered with time<1ms TTL=128 and Test-NetConnection returned the result above. If you would rather keep ping blocked, skip Test-NetConnection and test the TCP port directly with .NET. The check below waits up to 4 seconds and only prints True when the socket actually connected, because a connect attempt can finish with an error and still signal completion:
$t = New-Object Net.Sockets.TcpClient; $r = $t.BeginConnect('10.0.1.50', 8080, $null, $null); 'connected=' + ($r.AsyncWaitHandle.WaitOne(4000) -and $t.Connected); $t.Close()
With the rule in place it prints connected=True, and against a port the firewall drops it prints connected=False after 4 seconds. That is a quicker answer than Test-NetConnection gives when ping is dropped, so it is the check we would put in a deployment script. Leave out the $t.Connected test and the line can report success for an attempt that ended in an error.
Limit the port to one client address
An open port that every host on the network can reach is rarely what you want for a database or an admin service. Set-NetFirewallRule adds a source restriction to an existing rule, and the unique -Name from Step 1 makes sure you change exactly one rule. That only helps if it is the only rule for the port, because Windows lets traffic in when any allow rule matches. If you ran both the one-liner and the named version from Step 1, you have two 8080 rules, and scoping one leaves the other wide open. Count them first:
Get-NetFirewallRule -DisplayName "Allow TCP 8080" | Format-Table Name, DisplayName, Enabled
Exactly one row named app-tcp-8080 is what you want. If GUID rows show up next to it, remove every rule with that display name and recreate the named one, which is what we did in the lab:
Remove-NetFirewallRule -DisplayName "Allow TCP 8080"
New-NetFirewallRule -Name "app-tcp-8080" -DisplayName "Allow TCP 8080" -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow
With a single rule left, we first limited 8080 to a subnet our client is not in, to prove the restriction bites:
Set-NetFirewallRule -Name app-tcp-8080 -RemoteAddress 10.99.0.0/24
Get-NetFirewallRule -Name app-tcp-8080 | Get-NetFirewallAddressFilter | Format-List RemoteAddress, LocalAddress
Windows stores the subnet with a dotted mask, so do not be surprised when the CIDR you typed comes back rewritten:
RemoteAddress : 10.99.0.0/255.255.255.0
LocalAddress : Any
From the client at 10.0.1.51, the .NET check against 8080 now printed connected=False, even though the listener was still running. Pointing the rule at the client’s own address opened it again, and the same check printed connected=True:
Set-NetFirewallRule -Name app-tcp-8080 -RemoteAddress 10.0.1.51
-RemoteAddress takes a comma list, so an office subnet plus a jump host is one rule, for example -RemoteAddress 10.0.1.0/24,10.0.2.15. The same parameter works directly on New-NetFirewallRule when you create the rule.
Step 5: List and remove firewall rules
Unused allow rules are open doors, so clean up when a service goes away. To list only the rules you added yourself, filter out everything that belongs to a built-in group:
Get-NetFirewallRule -Direction Inbound | Where-Object { -not $_.DisplayGroup } | Sort-Object DisplayName | Format-Table Name, DisplayName, Enabled, Profile, Action
We took this listing after cleaning up the duplicates described in What broke in the lab, once the netsh 9443 test rule was gone and before the 8443 wizard rule existed. The range, syslog and web rules had been recreated with -Name at that point, which is why they show readable names instead of the GUIDs the Step 1 commands give you. It shows the rules from this guide, plus the OpenSSH rule the template already had:
Name DisplayName Enabled Profile Action
---- ----------- ------- ------- ------
{00b1af18-957e-410f-8c66-67e2ee8f3b49} Allow ICMPv4 Echo True Any Allow
app-tcp-5000-5010 Allow TCP 5000-5010 True Any Allow
app-tcp-8080 Allow TCP 8080 True Any Allow
{CD6356E4-0B4B-4C02-8AA4-B47C5B9D65FF} Allow UDP 161 SNMP True Any Allow
syslog-udp-514 Allow UDP 514 Syslog True Any Allow
web-tcp Allow Web Ports True Any Allow
sshd OpenSSH True Any Allow
You can tell where each rule came from by its Name. The readable ones were created with -Name, the lowercase GUID came from New-NetFirewallRule without it, and the uppercase GUID in braces is the SNMP rule netsh created. Before deleting by display name, preview what will go with -WhatIf. This is the run that caught us out, earlier in the lab while the duplicates still existed:
Remove-NetFirewallRule -DisplayName "Allow TCP 8080" -WhatIf
Three rules matched one display name, two duplicates from rerunning the one-liner and the named one:
What if: Remove-NetFirewallRule DisplayName: {e189950d-d6a7-4db2-aa1b-45402f3a6f4b}
What if: Remove-NetFirewallRule DisplayName: app-tcp-8080
What if: Remove-NetFirewallRule DisplayName: {14bb724b-8a8d-4592-8063-3324df091d49}
Dropping -WhatIf deletes all three, including any named rule you scoped earlier, so prefer Remove-NetFirewallRule -Name app-tcp-8080 when you mean exactly one rule. Running the delete again once nothing matches gives an error, which is a reliable way to confirm the rule is gone:
Remove-NetFirewallRule : No MSFT_NetFirewallRule objects found with property 'DisplayName' equal to 'Allow TCP 8080'. Verify the value of the property and retry.
The netsh delete removes every rule with that name in one go and tells you how many it found. After running the 9443 add command twice in Step 3, this is what we got:
netsh advfirewall firewall delete rule name="Allow TCP 9443"
The count gives the duplicate away:
Deleted 2 rule(s).
Ok.
A second delete prints No rules match the specified criteria., so that line is your proof the port is closed again on the firewall side. You get the same message for a typo in the name, so copy the name from show rule rather than retyping it.
What broke in the lab
Apart from the Test-NetConnection stall in Step 4, this run left us with duplicate rules, and you can create them on a real server just as easily. One of our guest commands timed out on the hypervisor side but still ran inside Windows, so when we ran the same batch again, every rule in it was created twice. The console showed it clearly:

- 1Two range rules
- 2Two syslog rules
- 3Two web port rules
- Two range rules for
Allow TCP 5000-5010. - Two syslog rules for
Allow UDP 514 Syslog. - Two web port rules for
Allow Web Ports.
Duplicates do not break traffic, but they make the firewall lie to you. If you later disable one copy to close a port, the other copy keeps it open. We removed them by display name and recreated each rule with a fixed -Name, and that is the habit we would keep for any server you manage with scripts, Ansible or a remote session that might drop and retry.
Ports you will open most often
Most requests to open a port on Windows Server come down to the same handful of services. Use the table to pick the protocol, then the one-liner from Step 1. For management ports such as RDP and WinRM, keep them off the internet entirely, and see the guide on enabling Remote Desktop on Windows Server for that side of the setup.
| Service | Port and protocol | Notes |
|---|---|---|
| HTTP / HTTPS (IIS) | 80/TCP, 443/TCP | IIS role adds its own rules |
| RDP | 3389/TCP | Restrict to trusted networks |
| SSH (OpenSSH Server) | 22/TCP | Check for an existing sshd rule first |
| SMB file sharing | 445/TCP | Never expose to the internet |
| WinRM | 5985/TCP, 5986/TCP | HTTP and HTTPS listeners |
| SQL Server | 1433/TCP | Default instance only |
| DNS | 53/TCP, 53/UDP | Both protocols |
| Syslog | 514/UDP | Used in Step 1 |
| SNMP | 161/UDP | Used in Step 3 |
Several roles create their rules for you when you install them, so check before adding your own. Our template already carried an sshd rule for port 22 from its OpenSSH Server setup, and adding a second one would only have created another duplicate. SQL Server is the opposite case, because a named instance or a custom port needs a manual rule, which the guide on making SQL Server listen on a given port walks through. If you are hosting sites, the IIS install guide and the walkthrough on IIS virtual directories cover the web side, and our pick of Windows Server administration books goes deeper on firewall policy through Group Policy.