[{"data":1,"prerenderedAt":2004},["ShallowReactive",2],{"nav-products":3,"article-\u002Farticles\u002Fhow_to\u002F2026-07-19-how-to-run-openvpn-on-aws-in-5-minutes\u002F":10,"article-latest-\u002Farticles\u002Fhow_to\u002F2026-07-19-how-to-run-openvpn-on-aws-in-5-minutes\u002F":133},[4],{"name":5,"tagline":6,"to":7,"isInternal":8,"icon":9},"Valdras Gate","Self-hosted OpenVPN® server on AWS — no licenses to manage","\u002Fproducts\u002Fgate\u002F",true,"\u002Fpublic\u002Fimg\u002Fproducts\u002Fgate\u002Fgate_logo.png",{"id":11,"title":12,"author":13,"body":14,"category":119,"cover":120,"date":121,"description":122,"extension":123,"featured":124,"isFeatured":125,"meta":126,"navigation":8,"path":127,"preview":128,"seo":129,"stem":130,"updated":131,"__hash__":132},"articles\u002Farticles\u002Fhow_to\u002F2026-07-19-how-to-run-openvpn-on-aws-in-5-minutes.md","How to run OpenVPN on AWS in 5 minutes","David Gatti",{"type":15,"value":16,"toc":115},"minimark",[17,21,26,29,32,36,39,62,66,69,91,94,97,101,104,108],[18,19,20],"p",{},"Setting up a VPN server used to take weeks t oget it right: fiddling with certificates, managing a CA, messing with iptables, and hoping it all works. This guide shows you how to get OpenVPN running in your own AWS account in about 5 minutes, and why doing it yourself beats renting one.",[22,23,25],"h1",{"id":24},"why-run-your-own-vpn-on-aws","Why run your own VPN on AWS",[18,27,28],{},"If you run stuff on AWS, you've got private subnets holding sensitive things, databases, admin panels, internal tools, that should never be public. The real question isn't keeping them private, but how your team gets to them. The answer: a VPN in a public subnet of your VPC. Your team connects to it, and those private subnets act like they're local. You control access with certs you issue, set expiration, and can revoke anytime someone leaves.",[18,30,31],{},"This setup works great for offices too. Rather than installing a VPN on each laptop, you just connect the office router to the gateway. That way, everyone in the office accesses AWS resources like they're local. One tunnel, no client setup, everyone behind the router is covered.",[22,33,35],{"id":34},"the-options","The options",[18,37,38],{},"You have three choices:",[40,41,42,50,56],"ol",{},[43,44,45,49],"li",{},[46,47,48],"strong",{},"AWS Client VPN",", managed, but pricey. You pay per subnet per hour plus per connected client. A small team can rack up hundreds every month.",[43,51,52,55],{},[46,53,54],{},"Build OpenVPN yourself",", launch an EC2, install OpenVPN, set up the CA, manage certs, and routing. Cheap except your time, and cert management never ends.",[43,57,58,61],{},[46,59,60],{},"Prebuilt OpenVPN AMI from AWS Marketplace",", the sweet spot. Setup in 5 minutes, cert management baked in, no headaches.",[22,63,65],{"id":64},"the-5-minute-setup","The 5 minute setup",[18,67,68],{},"We built Valdras Gate for this. Self-hosted OpenVPN in your AWS account, no license limits, unlimited users. Here's how:",[40,70,71,82,85,88],{},[43,72,73,74,81],{},"Subscribe at the ",[75,76,80],"a",{"href":77,"rel":78},"https:\u002F\u002Faws.amazon.com\u002Fmarketplace\u002Fpp\u002Fprodview-ladycxtlsdnd6",[79],"nofollow","Valdras Gate AWS Marketplace page",".",[43,83,84],{},"Launch the AMI in your VPC. A t3.nano handles a small team just fine, the network card is the only real bottleneck.",[43,86,87],{},"After boot, SSH in and create your first user with one CLI command. The user profile with embedded certs is emailed automatically.",[43,89,90],{},"Import the profile into any OpenVPN client and connect.",[18,92,93],{},"Done. No license servers, no fees per user, and the certificates manage themselves. User management is just a few CLI commands: create, list, revoke users. Each profile must have an expiration date set at creation, no exceptions. This is your safety net: if you forget to revoke a contractor's access, their certificate simply stops working once it expires. The server blocks it during handshake, so no manual cleanup is needed.",[18,95,96],{},"So, what about legit users who need ongoing access? They don't get locked out. Just enable auto-renewal for their profiles. The server will generate a new profile before the old one expires and email it with instructions. No one needs to keep track of expiry dates or submit tickets when VPN stops working Monday morning.",[22,98,100],{"id":99},"the-cost","The cost",[18,102,103],{},"Valdras Gate is a straightforward $99 a month, no matter your user count. Other VPNs on AWS Marketplace charge per user and force you to manually renew profiles every year. Once you have more than a few users, costs skyrocket and maintaining profiles becomes a huge headache. With our flat fee and automatic renewals, those issues just vanish.",[22,105,107],{"id":106},"closing-thoughts","Closing thoughts",[18,109,110,111,81],{},"VPNs should be boring. Set it up once, let it run, revoke certs when people leave. Running your own in AWS means you control security, not someone else. If you want the 5-minute setup instead of a weekend stress fest, ",[75,112,114],{"href":77,"rel":113},[79],"Valdras Gate is on AWS Marketplace",{"title":116,"searchDepth":117,"depth":117,"links":118},"",2,[],"how_to","\u002Fpublic\u002Fimg\u002Farticles\u002Fhow-to-run-openvpn-on-aws\u002Fcover.jpg","2026-07-19","Why running your own OpenVPN server in your AWS account beats commercial VPNs and managed offerings - and how to get one running in about 5 minutes.","md","\u002Fpublic\u002Fimg\u002Farticles\u002Fhow-to-run-openvpn-on-aws\u002Ffeatured.jpg",false,{},"\u002Farticles\u002Fhow_to\u002F2026-07-19-how-to-run-openvpn-on-aws-in-5-minutes","\u002Fpublic\u002Fimg\u002Farticles\u002Fhow-to-run-openvpn-on-aws\u002Fpreview.jpg",{"title":12,"description":122},"articles\u002Fhow_to\u002F2026-07-19-how-to-run-openvpn-on-aws-in-5-minutes",null,"zF8RKVLk8sfxnMg7b34TEcak9dYg2ThWW9fcZkd3JCU",[134,203,1939],{"id":135,"title":136,"author":13,"body":137,"category":192,"cover":193,"date":194,"description":195,"extension":123,"featured":196,"isFeatured":125,"meta":197,"navigation":8,"path":198,"preview":199,"seo":200,"stem":201,"updated":194,"__hash__":202},"articles\u002Farticles\u002Fexperience\u002F2026-09-12-aws-cost-optimization-fixing-a-40000-month-aws-bill.md","AWS Cost Optimization: Fixing a $40,000\u002FMonth AWS Bill",{"type":15,"value":138,"toc":190},[139,142,145,148,151,154,157,160,172,175,178,181,184,187],[18,140,141],{},"The day I worked on a project that was basically giving AWS a BMW every month, just because the engineering team had no idea how cookie-based sessions worked.",[18,143,144],{},"Yes, not knowing how cookie-based sessions work can cost you $40K a month. Crazy, right? A cookie is basically a tiny piece of data stored by the browser, sometimes containing just a small amount of data to identify the user or their session and load their unique data from the DB, yet misunderstanding how sessions work can turn that into a $40K monthly AWS bill. Three certified AWS experts came up with the following solution: use ALB sticky sessions, so AWS would keep each visitor pinned to a specific server. And to handle 50 users, their implementation ended up running 250 servers just to make sure each visitor could get their own unique server and avoid overwriting each other’s data and causing confusion.",[18,146,147],{},"Then people have the audacity to say AWS is expensive.",[18,149,150],{},"And this is the problem with the industry: the more time passes, the less humanity remembers. In the case of technology, that loss of knowledge is incredibly expensive and causes massive waste in spending, which then gets blamed on the third party, in this case AWS. But even if you had your own servers in the office, you would have bought $100K worth of hardware to solve the exact same problem. The difference is that if you figure out the problem and implement the fix in AWS, you reduce the costs immediately. If you did the same with office hardware, you’d be stuck with unused servers. Sure, you could sell them, but what are the chances there would be someone in the office with the time to manage eBay listings?",[18,152,153],{},"The majority of high cloud bills are just a skill issue, laziness, and ego. I worked at companies where they had big RDS instances not because they were needed, but because of very poorly designed queries and the unwillingness to take responsibility and spend the time to improve them, redesign the schema in a way that was more efficient, even bother with indexes, or leverage features of the DB engine that would improve performance. Not to mention the times the DB had to be big, not because of load, but because the data we had was serious.",[18,155,156],{},"So yes, blame cloud providers all you want. Regardless of where inefficient code is running, you will still pay a bigger bill than necessary if your team lacks the knowledge or willingness to fix the underlying problems, and if leadership is not technical enough to recognize when to push for a better solution.",[18,158,159],{},"So it is a knowledge problem, not a cost problem. When I hear that AWS is expensive, I see short-sightedness and a lack of understanding of what AWS, or the cloud in general, has to offer. Let’s consider RDS, which you might think of as just a regular database, and ask: why pay extra for what AWS offers when I can run it in my own colocation center, or spin up an EC2 instance, install something like PostgreSQL, and be done with it? What you lose is all the operational work AWS handles for you:",[161,162,163,166,169],"ul",{},[43,164,165],{},"Minor updates that, when automatic minor version upgrades are enabled, happen during the maintenance window without you having to install them manually. You do not have to worry about them, manage them, or deal with the work itself; it is handled automatically for you.",[43,167,168],{},"Multi-AZ DB instances, where you have a primary and a synchronous standby, not only for redundancy but, more importantly, to make database maintenance and failover much smoother. For underlying operating system maintenance, AWS can update the standby first and then switch traffic over before maintaining the old primary, with the failover typically lasting less than a minute. You do not have to manage that process at all; it just happens.",[43,170,171],{},"Major version upgrades that are almost as simple. You tell RDS to upgrade from version X to a supported version X1, and RDS handles the rest: running the engine upgrade workflow, performing upgrade checks, and, for RDS PostgreSQL when backup retention is enabled, taking snapshots before and after the upgrade.",[18,173,174],{},"It costs more, but what you get for that price is that you simply do not have to think about the plumbing. Otherwise, you need someone who, on a daily basis, checks, maintains, updates, and takes responsibility for your database, and that can become an incredible amount of work. Of course, you can say, “Pff, I will never upgrade the database. I’ll just leave it on the version I started with and be done with it.” Sure. Then expose an old, unpatched database to the public and increase the risk that a vulnerability results in the data leaking, getting deleted, or, worse, getting modified. You do you, but what AWS provides is incredibly cost-effective, transparent to maintain, and it just works. In my career, I have had to upgrade so many different types of databases that I would never have enough time to learn all the intricacies of every database engine and how to perform every update and upgrade manually, let alone reproduce the smooth transition AWS provides. On AWS, when the target version is supported, I can simply say, “Upgrade from version X to X1,” and the job is done.",[18,176,177],{},"Do we also want to talk about power consumption? With AWS, you get no separate power bill. In a colocation center, you have to pay for the power you use, and if you need more, you have to ask and provision for more. With AWS, who knows, who cares? I want the biggest, meanest server available to crunch data for one hour. No problem. It might consume a ridiculous amount of power, but all you see is the AWS bill for the resources you used.",[18,179,180],{},"And that brings me back to the project burning $40K a month to serve roughly 50 users. AWS was not the expensive part. The misunderstanding was. AWS simply made it incredibly easy to turn a software problem into 250 servers.",[18,182,183],{},"But it also made it incredibly easy to undo the mistake.",[18,185,186],{},"Once you understand the problem, you fix the application, terminate the unnecessary servers, and the compute cost disappears immediately. If you had solved the same problem by buying physical servers, now you have a pile of hardware you need to sell. If those servers were sitting in a colocation center, you might also have committed power capacity that you are still paying for even though you no longer need it, until your provider lets you reduce that commitment or the contract comes up for renewal.",[18,188,189],{},"That is the part people miss. The cloud makes it extremely easy to waste money, but it also makes it extremely easy to stop wasting it.",{"title":116,"searchDepth":117,"depth":117,"links":191},[],"experience","\u002Fpublic\u002Fimg\u002Farticles\u002Faws-cost-optimization-fixing-a-40000-month-aws-bill\u002Fcover.jpg","2026-09-12","Why DynamoDB plus S3 is the best serverless database combo on AWS - fast UI data in DynamoDB, cheap bulk data in S3, and you pay only for what you use.","\u002Fpublic\u002Fimg\u002Farticles\u002Faws-cost-optimization-fixing-a-40000-month-aws-bill\u002Ffeatured.jpg",{},"\u002Farticles\u002Fexperience\u002F2026-09-12-aws-cost-optimization-fixing-a-40000-month-aws-bill","\u002Fpublic\u002Fimg\u002Farticles\u002Faws-cost-optimization-fixing-a-40000-month-aws-bill\u002Fpreview.jpg",{"title":136,"description":195},"articles\u002Fexperience\u002F2026-09-12-aws-cost-optimization-fixing-a-40000-month-aws-bill","9SowfbkgXzbbM61GzOm_no9M_Rjx9uqGn7am-1xUkZQ",{"id":204,"title":205,"author":13,"body":206,"category":119,"cover":1929,"date":1930,"description":1931,"extension":123,"featured":1932,"isFeatured":125,"meta":1933,"navigation":8,"path":1934,"preview":1935,"seo":1936,"stem":1937,"updated":131,"__hash__":1938},"articles\u002Farticles\u002Fhow_to\u002F2026-08-14-how-to-host-a-legacy-aspnet-framework-application-on-aws-fargate.md","How to Host a Legacy ASP.NET Framework Application on AWS Fargate",{"type":15,"value":207,"toc":1881},[208,219,226,233,238,243,246,249,252,255,259,262,269,273,276,282,376,389,392,395,399,410,413,466,473,480,484,487,491,494,497,562,566,569,575,582,586,593,596,600,607,617,621,625,628,635,642,646,649,652,655,658,662,665,696,701,705,712,717,780,786,790,797,800,811,814,822,829,836,839,848,852,855,872,875,888,891,894,898,901,904,907,911,915,918,921,938,941,945,948,951,957,961,966,1036,1039,1043,1046,1076,1079,1094,1098,1101,1110,1113,1122,1125,1131,1134,1140,1144,1150,1176,1179,1185,1188,1191,1195,1198,1215,1218,1221,1224,1241,1244,1247,1251,1254,1261,1263,1288,1291,1297,1300,1306,1309,1312,1393,1396,1399,1402,1406,1409,1412,1418,1421,1427,1437,1440,1444,1451,1461,1464,1472,1476,1483,1721,1724,1731,1734,1745,1748,1751,1757,1760,1766,1772,1775,1779,1782,1785,1788,1791,1794,1797,1801,1805,1808,1812,1815,1819,1822,1826,1829,1833,1839,1843,1852,1856,1863,1867,1870,1874,1877],[18,209,210,211,214,215,218],{},"If you need to ",[46,212,213],{},"host a legacy"," ASP.NET ",[46,216,217],{},"Framework application on AWS",", you don't necessarily need to rewrite it or move your infrastructure to Azure.",[18,220,221,222,225],{},"AWS Fargate can run ASP.NET ",[46,223,224],{},"Framework applications in Windows containers"," using Amazon ECS, allowing you to run existing IIS applications on AWS without managing Windows servers yourself.",[18,227,228,229,232],{},"In this guide, I'll show you how to containerize a legacy ASP.NET Framework application, deploy it to ",[46,230,231],{},"Amazon ECS with AWS Fargate",", handle ASP.NET session state, configure load balancer health checks, and understand the costs and limitations of running Windows containers on Fargate.",[234,235,237],"h2",{"id":236},"downsides","Downsides",[239,240,242],"h3",{"id":241},"windows-container-image-size","Windows container image size",[18,244,245],{},"The main downside of running ASP.NET on Fargate is the size of Windows container images.",[18,247,248],{},"For traditional ASP.NET applications running on the .NET Framework, Microsoft recommends using Windows Server Core as the base image. Windows container images are considerably larger than the Linux images you may be used to working with. For reference, the Windows Server 2022 Server Core image was around 2.76 GB uncompressed when Windows Server 2022 was released.",[18,250,251],{},"The final image will be larger once you add ASP.NET, the .NET Framework, your application, and any other dependencies. There is no fixed image size, so the actual size depends on the Windows version, Microsoft image, and your application.",[18,253,254],{},"This becomes important when deploying or autoscaling the application. When Fargate starts a new Windows task, it needs to download the container image before the container can start. Large images therefore mean longer startup times, which is something you need to account for when configuring autoscaling.",[239,256,258],{"id":257},"minimum-windows-fargate-task-size","Minimum Windows Fargate task size",[18,260,261],{},"The second downside is the minimum amount of compute you can allocate.",[18,263,264,265,268],{},"Windows containers on Fargate require at least ",[46,266,267],{},"1 vCPU and 2 GB of memory",". With Linux containers, Fargate can go as low as 0.25 vCPU and 512 MB of memory. This means that even a very small ASP.NET application has a higher minimum running cost simply because it is running on Windows.",[239,270,272],{"id":271},"windows-fargate-pricing","Windows Fargate pricing",[18,274,275],{},"Windows containers are also considerably more expensive to run on Fargate than Linux containers.",[18,277,278,279,281],{},"The smallest Windows Fargate task you can run has ",[46,280,267],{},". Using the current AWS pricing for US East (N. Virginia), one task costs approximately:",[283,284,285,299],"table",{},[286,287,288],"thead",{},[289,290,291,294,297],"tr",{},[292,293],"th",{},[292,295],{"align":296},"right",[292,298],{"align":296},[300,301,302,311,322,333,344,359],"tbody",{},[289,303,304,307,309],{},[305,306],"td",{},[305,308],{"align":296},[305,310],{"align":296},[289,312,313,316,319],{},[305,314,315],{},"Charge",[305,317,318],{"align":296},"Per hour",[305,320,321],{"align":296},"30 days, 24\u002F7",[289,323,324,327,330],{},[305,325,326],{},"1 vCPU",[305,328,329],{"align":296},"$0.0915",[305,331,332],{"align":296},"$65.88",[289,334,335,338,341],{},[305,336,337],{},"2 GB memory",[305,339,340],{"align":296},"$0.0200",[305,342,343],{"align":296},"$14.40",[289,345,346,349,354],{},[305,347,348],{},"Windows OS charge",[305,350,351],{"align":296},[46,352,353],{},"$0.0460",[305,355,356],{"align":296},[46,357,358],{},"$33.12",[289,360,361,366,371],{},[305,362,363],{},[46,364,365],{},"Total",[305,367,368],{"align":296},[46,369,370],{},"$0.1575",[305,372,373],{"align":296},[46,374,375],{},"$113.40",[18,377,378,379,381,382,385,386,81],{},"The important part here is the ",[46,380,348],{},". AWS adds approximately ",[46,383,384],{},"$0.046 per vCPU per hour"," specifically for running Windows. With the minimum 1 vCPU task running continuously, that is about ",[46,387,388],{},"$33.12 per month just for the Windows operating system charge",[18,390,391],{},"This is billed by AWS as a Windows OS charge. You are not paying Microsoft directly or buying a separate Windows license yourself; the Windows licensing cost is included as part of the Fargate billing.",[18,393,394],{},"It is also important to understand that the Windows OS charge is not the only reason Windows costs more. AWS also charges more for the Windows vCPU and memory compared with Linux.",[239,396,398],{"id":397},"windows-vs-linux-cost","Windows vs Linux cost",[18,400,401,402,405,406,409],{},"For comparison, an equivalent Linux Fargate task with 1 vCPU and 2 GB of memory costs approximately ",[46,403,404],{},"$0.0494 per hour",", or about ",[46,407,408],{},"$35.55 per month"," running continuously.",[18,411,412],{},"So the comparison looks like this:",[283,414,415,423],{},[286,416,417],{},[289,418,419,421],{},[292,420],{},[292,422],{"align":296},[300,424,425,431,439,447,454],{},[289,426,427,429],{},[305,428],{},[305,430],{"align":296},[289,432,433,436],{},[305,434,435],{},"Task",[305,437,438],{"align":296},"Approx. monthly cost",[289,440,441,444],{},[305,442,443],{},"Linux — 1 vCPU \u002F 2 GB",[305,445,446],{"align":296},"$35.55",[289,448,449,452],{},[305,450,451],{},"Windows — 1 vCPU \u002F 2 GB",[305,453,375],{"align":296},[289,455,456,461],{},[305,457,458],{},[46,459,460],{},"Difference",[305,462,463],{"align":296},[46,464,465],{},"$77.85",[18,467,468,469,472],{},"Of that $77.85 difference, approximately ",[46,470,471],{},"$33.12 is the explicit Windows OS charge",". The remaining difference comes from the higher Windows Fargate CPU and memory rates.",[18,474,475,476,479],{},"In other words, the same 1 vCPU and 2 GB task costs roughly ",[46,477,478],{},"3.2 times more on Windows than on Linux"," before adding things such as load balancers, data transfer, CloudWatch, public IPv4 addresses, or other AWS services.",[239,481,483],{"id":482},"supported-windows-versions","Supported Windows versions",[18,485,486],{},"AWS currently supports Windows Server 2019 and Windows Server 2022 containers on Fargate, in both Full and Core variants.",[239,488,490],{"id":489},"unsupported-features-on-windows-fargate","Unsupported features on Windows Fargate",[18,492,493],{},"There are also several AWS features available to Linux containers on Fargate that are not currently supported when running Windows containers.",[18,495,496],{},"According to the AWS documentation, Windows containers on Fargate do not support:",[161,498,499,504,509,514,519,524,529,534,539,544,552,557],{},[43,500,501],{},[46,502,503],{},"Amazon FSx",[43,505,506],{},[46,507,508],{},"Amazon EFS volumes",[43,510,511],{},[46,512,513],{},"Amazon EBS volumes",[43,515,516],{},[46,517,518],{},"Fargate Spot",[43,520,521],{},[46,522,523],{},"ENI trunking",[43,525,526],{},[46,527,528],{},"gMSA (group Managed Service Accounts) for Windows containers",[43,530,531],{},[46,532,533],{},"AWS App Mesh service and proxy integration",[43,535,536],{},[46,537,538],{},"FireLens log router integration",[43,540,541],{},[46,542,543],{},"Image volumes",[43,545,546,547,551],{},"The ",[548,549,550],"code",{},"environmentFiles"," task definition parameter",[43,553,546,554,551],{},[548,555,556],{},"maxSwap",[43,558,546,559,551],{},[548,560,561],{},"swappiness",[239,563,565],{"id":564},"other-windows-fargate-limitations","Other Windows Fargate limitations",[18,567,568],{},"There are a few additional differences worth knowing about.",[18,570,546,571,574],{},[548,572,573],{},"VOLUME"," option inside a Dockerfile is ignored for Windows containers on Fargate. If your application needs local storage, AWS recommends using bind mounts defined in the ECS task definition instead.",[18,576,577,578,581],{},"Windows Fargate also does not support the ",[548,579,580],{},"ulimits"," task definition parameter, which Linux Fargate supports.",[239,583,585],{"id":584},"seekable-oci-support","Seekable OCI support",[18,587,588,589,592],{},"Another important limitation is ",[46,590,591],{},"Seekable OCI (SOCI)",". On Linux, Fargate can use SOCI to start a container before the entire image has been downloaded. This is particularly useful with large container images. Windows containers on Fargate do not support SOCI, so the complete Windows container image needs to be downloaded before the container can start.",[18,594,595],{},"This matters for ASP.NET applications because Windows container images are already relatively large. Without SOCI lazy loading, the size of the image has a direct impact on how quickly a new task can start during a deployment or autoscaling event.",[239,597,599],{"id":598},"architecture-and-task-size-limitations","Architecture and task size limitations",[18,601,602,603,606],{},"Windows Fargate tasks are also limited to the ",[46,604,605],{},"x86-64 architecture",". Linux Fargate can run on both x86-64 and ARM64.",[18,608,609,610,612,613,616],{},"Finally, Windows Fargate supports a smaller range of task sizes. Windows starts at ",[46,611,267],{}," and currently goes up to ",[46,614,615],{},"4 vCPU and 30 GB of memory",". Linux Fargate additionally supports 0.25 and 0.5 vCPU configurations at the low end and 8, 16, and 32 vCPU configurations at the high end.",[234,618,620],{"id":619},"making-aspnet-stateless","Making ASP.NET stateless",[239,622,624],{"id":623},"how-aspnet-sessions-work-by-default","How ASP.NET sessions work by default",[18,626,627],{},"Other than these differences, there is nothing stopping you from running and scaling an ASP.NET application on Fargate. The main thing you need to pay attention to is how the application handles user sessions.",[18,629,630,631,634],{},"By default, classic ASP.NET Framework stores session data using ",[548,632,633],{},"InProc"," mode. This means the actual session data is stored in the RAM of the IIS worker process running your application.",[18,636,637,638,641],{},"The browser only stores a cookie containing the session ID, such as ",[548,639,640],{},"ASP.NET_SessionId",". The actual data associated with that session remains in the memory of the server.",[239,643,645],{"id":644},"why-inproc-becomes-a-problem-with-fargate","Why InProc becomes a problem with Fargate",[18,647,648],{},"This works perfectly well when you have one server, but it becomes a problem when you start running multiple Fargate tasks behind a load balancer.",[18,650,651],{},"For example, a user's first request might go to Task A, where their session is created and stored in memory. Their next request might go to Task B. The browser will still send the same session ID, but Task B does not have the corresponding session data because it exists only in the memory of Task A.",[18,653,654],{},"The same problem occurs if Task A crashes, is replaced during a deployment, or is terminated by autoscaling. Everything stored in its memory disappears with it.",[18,656,657],{},"Fortunately, ASP.NET Framework already has a solution for this, and in many cases you do not need to rewrite your application's session handling.",[239,659,661],{"id":660},"aspnet-session-state-options","ASP.NET session-state options",[18,663,664],{},"ASP.NET supports several session-state modes:",[161,666,667,672,678,684,690],{},[43,668,669,671],{},[548,670,633],{}," — stores session data in the memory of the IIS process. This is the default.",[43,673,674,677],{},[548,675,676],{},"StateServer"," — stores session data in a separate ASP.NET State Service.",[43,679,680,683],{},[548,681,682],{},"SQLServer"," — stores session data in SQL Server.",[43,685,686,689],{},[548,687,688],{},"Custom"," — allows you to use your own session-state provider.",[43,691,692,695],{},[548,693,694],{},"Off"," — disables ASP.NET session state completely.",[18,697,698,699,81],{},"For a Fargate deployment, the important part is moving the session state outside of ",[548,700,633],{},[239,702,704],{"id":703},"store-aspnet-sessions-in-sql-server","Store ASP.NET sessions in SQL Server",[18,706,707,708,711],{},"For example, you can configure ASP.NET to store sessions in SQL Server directly from ",[548,709,710],{},"Web.config",":",[713,714,716],"h4",{"id":715},"configure-webconfig","Configure Web.config",[718,719,723],"pre",{"className":720,"code":721,"language":722,"meta":116,"style":116},"language-xml shiki shiki-themes github-light github-dark","\u003Cconfiguration>\n  \u003Csystem.web>\n    \u003CsessionState\n      mode=\"SQLServer\"\n      sqlConnectionString=\"data source=YOUR_SQL_SERVER;user id=USERNAME;password=PASSWORD\"\n      cookieless=\"false\"\n      timeout=\"20\" \u002F>\n  \u003C\u002Fsystem.web>\n\u003C\u002Fconfiguration>\n","xml",[548,724,725,733,738,744,750,756,762,768,774],{"__ignoreMap":116},[726,727,730],"span",{"class":728,"line":729},"line",1,[726,731,732],{},"\u003Cconfiguration>\n",[726,734,735],{"class":728,"line":117},[726,736,737],{},"  \u003Csystem.web>\n",[726,739,741],{"class":728,"line":740},3,[726,742,743],{},"    \u003CsessionState\n",[726,745,747],{"class":728,"line":746},4,[726,748,749],{},"      mode=\"SQLServer\"\n",[726,751,753],{"class":728,"line":752},5,[726,754,755],{},"      sqlConnectionString=\"data source=YOUR_SQL_SERVER;user id=USERNAME;password=PASSWORD\"\n",[726,757,759],{"class":728,"line":758},6,[726,760,761],{},"      cookieless=\"false\"\n",[726,763,765],{"class":728,"line":764},7,[726,766,767],{},"      timeout=\"20\" \u002F>\n",[726,769,771],{"class":728,"line":770},8,[726,772,773],{},"  \u003C\u002Fsystem.web>\n",[726,775,777],{"class":728,"line":776},9,[726,778,779],{},"\u003C\u002Fconfiguration>\n",[18,781,546,782,785],{},[548,783,784],{},"mode=\"SQLServer\""," setting tells ASP.NET to stop keeping session state inside the IIS process and use SQL Server instead.",[239,787,789],{"id":788},"prepare-sql-server-for-session-state","Prepare SQL Server for session state",[18,791,792,793,796],{},"Before this works, the SQL Server needs the ASP.NET session-state database and stored procedures. Microsoft provides the ",[548,794,795],{},"aspnet_regsql.exe"," utility for doing this.",[18,798,799],{},"For example:",[718,801,805],{"className":802,"code":803,"language":804,"meta":116,"style":116},"language-powershell shiki shiki-themes github-light github-dark","aspnet_regsql.exe -S YOUR_SQL_SERVER -E -ssadd -sstype p\n","powershell",[548,806,807],{"__ignoreMap":116},[726,808,809],{"class":728,"line":729},[726,810,803],{},[18,812,813],{},"The important option here is:",[718,815,820],{"className":816,"code":818,"language":819,"meta":116},[817],"language-text","-sstype p\n","text",[548,821,818],{"__ignoreMap":116},[18,823,824,825,828],{},"This tells ASP.NET to store the session data persistently inside the ",[548,826,827],{},"ASPState"," database.",[18,830,831,832,835],{},"Without this option, the default configuration stores the session data in SQL Server's ",[548,833,834],{},"tempdb",". That still allows multiple Fargate tasks to share sessions, but the session data will disappear if SQL Server itself is restarted.",[18,837,838],{},"If SQL authentication is being used instead of Windows authentication, the same setup can be performed using a username and password:",[718,840,842],{"className":802,"code":841,"language":804,"meta":116,"style":116},"aspnet_regsql.exe -S YOUR_SQL_SERVER -U USERNAME -P PASSWORD -ssadd -sstype p\n",[548,843,844],{"__ignoreMap":116},[726,845,846],{"class":728,"line":729},[726,847,841],{},[239,849,851],{"id":850},"existing-application-code-does-not-need-to-change","Existing application code does not need to change",[18,853,854],{},"Once this is configured, existing application code such as:",[718,856,860],{"className":857,"code":858,"language":859,"meta":116,"style":116},"language-csharp shiki shiki-themes github-light github-dark","Session[\"UserId\"] = 123;\nSession[\"Cart\"] = cart;\n","csharp",[548,861,862,867],{"__ignoreMap":116},[726,863,864],{"class":728,"line":729},[726,865,866],{},"Session[\"UserId\"] = 123;\n",[726,868,869],{"class":728,"line":117},[726,870,871],{},"Session[\"Cart\"] = cart;\n",[18,873,874],{},"normally does not need to change. ASP.NET handles storing and retrieving the session from SQL Server instead of keeping it in local memory.",[18,876,877,878,881,882,884,885,887],{},"One thing to be aware of is that objects placed inside ",[548,879,880],{},"Session"," must be serializable when using ",[548,883,682],{}," mode. With ",[548,886,633],{},", ASP.NET can keep arbitrary objects directly in memory, but once session state is moved outside the IIS process, ASP.NET needs to serialize those objects before storing them.",[18,889,890],{},"Once the session data is stored in a shared location, it no longer matters which Fargate task receives the next request. Every task can access the same session data.",[18,892,893],{},"This means ECS can start additional tasks when traffic increases, terminate tasks when traffic decreases, replace unhealthy tasks, or deploy a new version of the application without destroying the user's session.",[239,895,897],{"id":896},"sticky-sessions-vs-shared-session-state","Sticky sessions vs shared session state",[18,899,900],{},"Another option is to enable sticky sessions on the Application Load Balancer, which attempts to keep the same user connected to the same Fargate task. While this can work, it does not make the application truly stateless. If that particular task disappears, its in-memory session disappears with it.",[18,902,903],{},"Moving session state outside the container is therefore the better approach when you want to take full advantage of Fargate and ECS autoscaling.",[18,905,906],{},"Once your application no longer depends on data stored inside a specific task, you can run multiple copies behind an Application Load Balancer and scale them up and down like any other containerized application on AWS.",[234,908,910],{"id":909},"building-the-aspnet-docker-image","Building the ASP.NET Docker image",[239,912,914],{"id":913},"what-the-microsoft-base-image-includes","What the Microsoft base image includes",[18,916,917],{},"Putting an ASP.NET Framework application inside a container is simpler than it might initially seem.",[18,919,920],{},"Microsoft already provides an official ASP.NET Framework container image that includes:",[161,922,923,926,929,932,935],{},[43,924,925],{},"Windows Server Core",[43,927,928],{},"IIS 10",[43,930,931],{},".NET Framework",[43,933,934],{},"ASP.NET support for IIS",[43,936,937],{},"The service required to keep IIS running inside the container",[18,939,940],{},"This means that you do not need to install Windows, IIS, or ASP.NET yourself.",[239,942,944],{"id":943},"create-a-minimal-aspnet-application","Create a minimal ASP.NET application",[18,946,947],{},"For a very basic example, we can create a container that runs a single ASP.NET page.",[18,949,950],{},"Create a directory containing these two files:",[718,952,955],{"className":953,"code":954,"language":819,"meta":116},[817],"Dockerfile\nDefault.aspx\n",[548,956,954],{"__ignoreMap":116},[713,958,960],{"id":959},"defaultaspx","Default.aspx",[18,962,546,963,965],{},[548,964,960],{}," file can be as simple as:",[718,967,971],{"className":968,"code":969,"language":970,"meta":116,"style":116},"language-aspx shiki shiki-themes github-light github-dark","\u003C%@ Page Language=\"C#\" %>\n\n\u003C!DOCTYPE html>\n\u003Chtml>\n\u003Chead>\n    \u003Ctitle>ASP.NET on AWS\u003C\u002Ftitle>\n\u003C\u002Fhead>\n\u003Cbody>\n    \u003Ch1>Hello World from ASP.NET\u003C\u002Fh1>\n    \u003Cp>Server time: \u003C%= DateTime.UtcNow %>\u003C\u002Fp>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n","aspx",[548,972,973,978,983,988,993,998,1003,1008,1013,1018,1024,1030],{"__ignoreMap":116},[726,974,975],{"class":728,"line":729},[726,976,977],{},"\u003C%@ Page Language=\"C#\" %>\n",[726,979,980],{"class":728,"line":117},[726,981,982],{"emptyLinePlaceholder":8},"\n",[726,984,985],{"class":728,"line":740},[726,986,987],{},"\u003C!DOCTYPE html>\n",[726,989,990],{"class":728,"line":746},[726,991,992],{},"\u003Chtml>\n",[726,994,995],{"class":728,"line":752},[726,996,997],{},"\u003Chead>\n",[726,999,1000],{"class":728,"line":758},[726,1001,1002],{},"    \u003Ctitle>ASP.NET on AWS\u003C\u002Ftitle>\n",[726,1004,1005],{"class":728,"line":764},[726,1006,1007],{},"\u003C\u002Fhead>\n",[726,1009,1010],{"class":728,"line":770},[726,1011,1012],{},"\u003Cbody>\n",[726,1014,1015],{"class":728,"line":776},[726,1016,1017],{},"    \u003Ch1>Hello World from ASP.NET\u003C\u002Fh1>\n",[726,1019,1021],{"class":728,"line":1020},10,[726,1022,1023],{},"    \u003Cp>Server time: \u003C%= DateTime.UtcNow %>\u003C\u002Fp>\n",[726,1025,1027],{"class":728,"line":1026},11,[726,1028,1029],{},"\u003C\u002Fbody>\n",[726,1031,1033],{"class":728,"line":1032},12,[726,1034,1035],{},"\u003C\u002Fhtml>\n",[18,1037,1038],{},"Using the server time is useful here because it confirms that the page is actually being processed by ASP.NET rather than simply being served by IIS as a static HTML file.",[239,1040,1042],{"id":1041},"create-the-dockerfile","Create the Dockerfile",[18,1044,1045],{},"The Dockerfile itself can then be:",[718,1047,1051],{"className":1048,"code":1049,"language":1050,"meta":116,"style":116},"language-dockerfile shiki shiki-themes github-light github-dark","FROM mcr.microsoft.com\u002Fdotnet\u002Fframework\u002Faspnet:4.8.1-windowsservercore-ltsc2022\n\nWORKDIR C:inetpubwwwroot\n\nCOPY Default.aspx .\n","dockerfile",[548,1052,1053,1058,1062,1067,1071],{"__ignoreMap":116},[726,1054,1055],{"class":728,"line":729},[726,1056,1057],{},"FROM mcr.microsoft.com\u002Fdotnet\u002Fframework\u002Faspnet:4.8.1-windowsservercore-ltsc2022\n",[726,1059,1060],{"class":728,"line":117},[726,1061,982],{"emptyLinePlaceholder":8},[726,1063,1064],{"class":728,"line":740},[726,1065,1066],{},"WORKDIR C:inetpubwwwroot\n",[726,1068,1069],{"class":728,"line":746},[726,1070,982],{"emptyLinePlaceholder":8},[726,1072,1073],{"class":728,"line":752},[726,1074,1075],{},"COPY Default.aspx .\n",[18,1077,1078],{},"And that is essentially all you need.",[18,1080,1081,1082,1085,1086,1089,1090,1093],{},"Microsoft's ASP.NET base image already exposes port ",[548,1083,1084],{},"80"," and starts the IIS ",[548,1087,1088],{},"w3svc"," service when the container starts, so you do not need to create your own ",[548,1091,1092],{},"ENTRYPOINT"," or install IIS manually.",[239,1095,1097],{"id":1096},"build-and-run-the-container-locally","Build and run the container locally",[18,1099,1100],{},"You can build the image with:",[718,1102,1104],{"className":802,"code":1103,"language":804,"meta":116,"style":116},"docker build -t aspnet-hello-world .\n",[548,1105,1106],{"__ignoreMap":116},[726,1107,1108],{"class":728,"line":729},[726,1109,1103],{},[18,1111,1112],{},"And run it locally with:",[718,1114,1116],{"className":802,"code":1115,"language":804,"meta":116,"style":116},"docker run --rm -p 8080:80 aspnet-hello-world\n",[548,1117,1118],{"__ignoreMap":116},[726,1119,1120],{"class":728,"line":729},[726,1121,1115],{},[18,1123,1124],{},"You can then open:",[718,1126,1129],{"className":1127,"code":1128,"language":819,"meta":116},[817],"http:\u002F\u002Flocalhost:8080\n",[548,1130,1128],{"__ignoreMap":116},[18,1132,1133],{},"and you should see:",[718,1135,1138],{"className":1136,"code":1137,"language":819,"meta":116},[817],"Hello World from ASP.NET\nServer time: ...\n",[548,1139,1137],{"__ignoreMap":116},[239,1141,1143],{"id":1142},"containerizing-a-real-aspnet-application","Containerizing a real ASP.NET application",[18,1145,1146,1147,1149],{},"For a real application, instead of copying ",[548,1148,960],{},", you would copy the published application into the same IIS directory:",[718,1151,1153],{"className":1048,"code":1152,"language":1050,"meta":116,"style":116},"FROM mcr.microsoft.com\u002Fdotnet\u002Fframework\u002Faspnet:4.8.1-windowsservercore-ltsc2022\n\nWORKDIR C:inetpubwwwroot\n\nCOPY .\u002Fpublish\u002F .\n",[548,1154,1155,1159,1163,1167,1171],{"__ignoreMap":116},[726,1156,1157],{"class":728,"line":729},[726,1158,1057],{},[726,1160,1161],{"class":728,"line":117},[726,1162,982],{"emptyLinePlaceholder":8},[726,1164,1165],{"class":728,"line":740},[726,1166,1066],{},[726,1168,1169],{"class":728,"line":746},[726,1170,982],{"emptyLinePlaceholder":8},[726,1172,1173],{"class":728,"line":752},[726,1174,1175],{},"COPY .\u002Fpublish\u002F .\n",[18,1177,1178],{},"The important part is that the application ultimately ends up inside:",[718,1180,1183],{"className":1181,"code":1182,"language":819,"meta":116},[817],"C:inetpubwwwroot\n",[548,1184,1182],{"__ignoreMap":116},[18,1186,1187],{},"From there, IIS inside the Microsoft container image handles the application in much the same way as IIS running on a normal Windows Server.",[18,1189,1190],{},"Once the image works locally, the same image can be pushed to Amazon ECR and used by an ECS Fargate task.",[234,1192,1194],{"id":1193},"precompiling-aspnet-before-deployment","Precompiling ASP.NET before deployment",[18,1196,1197],{},"Another thing worth considering with older ASP.NET applications is what happens when the application starts for the first time.",[18,1199,1200,1201,1204,1205,1204,1208,1204,1211,1214],{},"Classic ASP.NET can compile parts of the application dynamically when they are first requested. Depending on how the project was built, this can include ",[548,1202,1203],{},".aspx",", ",[548,1206,1207],{},".ascx",[548,1209,1210],{},"App_Code",[548,1212,1213],{},"Global.asax",", and other application resources.",[18,1216,1217],{},"On a traditional Windows server this might not be very noticeable because the server stays online for a long time. The compilation happens once, the result is cached, and future requests are fast.",[18,1219,1220],{},"With Fargate, things are slightly different.",[18,1222,1223],{},"Every time ECS starts a new task, you are starting a fresh Windows container with a fresh IIS instance. This can happen during:",[161,1225,1226,1229,1232,1235,1238],{},[43,1227,1228],{},"A new deployment",[43,1230,1231],{},"Autoscaling",[43,1233,1234],{},"Task replacement",[43,1236,1237],{},"An application crash",[43,1239,1240],{},"Infrastructure maintenance",[18,1242,1243],{},"If the application relies on runtime compilation, every new task may need to perform some of that work again before it can serve requests at full speed.",[18,1245,1246],{},"This means the first requests reaching a newly started task can be slower than normal.",[239,1248,1250],{"id":1249},"compile-the-application-during-the-build","Compile the application during the build",[18,1252,1253],{},"A better approach is to precompile as much of the ASP.NET application as possible before creating the final container image.",[18,1255,1256,1257,1260],{},"Microsoft provides ",[548,1258,1259],{},"aspnet_compiler.exe"," specifically for this purpose.",[18,1262,799],{},[718,1264,1266],{"className":802,"code":1265,"language":804,"meta":116,"style":116},"C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet_compiler.exe ^\n  -p C:src ^\n  -v \u002F ^\n  C:publish\n",[548,1267,1268,1273,1278,1283],{"__ignoreMap":116},[726,1269,1270],{"class":728,"line":729},[726,1271,1272],{},"C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet_compiler.exe ^\n",[726,1274,1275],{"class":728,"line":117},[726,1276,1277],{},"  -p C:src ^\n",[726,1279,1280],{"class":728,"line":740},[726,1281,1282],{},"  -v \u002F ^\n",[726,1284,1285],{"class":728,"line":746},[726,1286,1287],{},"  C:publish\n",[18,1289,1290],{},"This takes the ASP.NET application from:",[718,1292,1295],{"className":1293,"code":1294,"language":819,"meta":116},[817],"C:src\n",[548,1296,1294],{"__ignoreMap":116},[18,1298,1299],{},"and produces a precompiled version inside:",[718,1301,1304],{"className":1302,"code":1303,"language":819,"meta":116},[817],"C:publish\n",[548,1305,1303],{"__ignoreMap":116},[18,1307,1308],{},"The resulting files can then be copied into the final ASP.NET container image.",[18,1310,1311],{},"A multi-stage Dockerfile could look like this:",[718,1313,1315],{"className":1048,"code":1314,"language":1050,"meta":116,"style":116},"FROM mcr.microsoft.com\u002Fdotnet\u002Fframework\u002Fsdk:4.8.1-windowsservercore-ltsc2022 AS build\n\nWORKDIR C:src\n\nCOPY . .\n\nRUN C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet_compiler.exe ^\n    -p C:src ^\n    -v \u002F ^\n    C:publish\n\nFROM mcr.microsoft.com\u002Fdotnet\u002Fframework\u002Faspnet:4.8.1-windowsservercore-ltsc2022\n\nWORKDIR C:inetpubwwwroot\n\nCOPY --from=build C:publish .\n",[548,1316,1317,1322,1326,1331,1335,1340,1344,1349,1354,1359,1364,1368,1372,1377,1382,1387],{"__ignoreMap":116},[726,1318,1319],{"class":728,"line":729},[726,1320,1321],{},"FROM mcr.microsoft.com\u002Fdotnet\u002Fframework\u002Fsdk:4.8.1-windowsservercore-ltsc2022 AS build\n",[726,1323,1324],{"class":728,"line":117},[726,1325,982],{"emptyLinePlaceholder":8},[726,1327,1328],{"class":728,"line":740},[726,1329,1330],{},"WORKDIR C:src\n",[726,1332,1333],{"class":728,"line":746},[726,1334,982],{"emptyLinePlaceholder":8},[726,1336,1337],{"class":728,"line":752},[726,1338,1339],{},"COPY . .\n",[726,1341,1342],{"class":728,"line":758},[726,1343,982],{"emptyLinePlaceholder":8},[726,1345,1346],{"class":728,"line":764},[726,1347,1348],{},"RUN C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet_compiler.exe ^\n",[726,1350,1351],{"class":728,"line":770},[726,1352,1353],{},"    -p C:src ^\n",[726,1355,1356],{"class":728,"line":776},[726,1357,1358],{},"    -v \u002F ^\n",[726,1360,1361],{"class":728,"line":1020},[726,1362,1363],{},"    C:publish\n",[726,1365,1366],{"class":728,"line":1026},[726,1367,982],{"emptyLinePlaceholder":8},[726,1369,1370],{"class":728,"line":1032},[726,1371,1057],{},[726,1373,1375],{"class":728,"line":1374},13,[726,1376,982],{"emptyLinePlaceholder":8},[726,1378,1380],{"class":728,"line":1379},14,[726,1381,1066],{},[726,1383,1385],{"class":728,"line":1384},15,[726,1386,982],{"emptyLinePlaceholder":8},[726,1388,1390],{"class":728,"line":1389},16,[726,1391,1392],{},"COPY --from=build C:publish .\n",[18,1394,1395],{},"The first stage contains the development and build tools necessary to compile the application.",[18,1397,1398],{},"The second stage uses the normal ASP.NET runtime image and contains only the files required to actually run the application.",[18,1400,1401],{},"This also keeps the build tools out of the production container.",[234,1403,1405],{"id":1404},"health-checks-for-the-load-balancer","Health checks for the load balancer",[18,1407,1408],{},"When running ASP.NET on Fargate behind an Application Load Balancer, the load balancer needs a way to determine whether each Fargate task is actually ready to receive traffic.",[18,1410,1411],{},"The target group does this by periodically sending an HTTP request to a configured path, for example:",[718,1413,1416],{"className":1414,"code":1415,"language":819,"meta":116},[817],"\u002Fhealth\n",[548,1417,1415],{"__ignoreMap":116},[18,1419,1420],{},"or, for a classic ASP.NET Framework application:",[718,1422,1425],{"className":1423,"code":1424,"language":819,"meta":116},[817],"\u002Fhealth.aspx\n",[548,1426,1424],{"__ignoreMap":116},[18,1428,1429,1430,1433,1434,81],{},"The endpoint should return ",[548,1431,1432],{},"200 OK"," when the application is healthy and ready to receive traffic. If the application is not ready or a critical dependency is unavailable, it should return an error such as ",[548,1435,1436],{},"503 Service Unavailable",[18,1438,1439],{},"This prevents the load balancer from sending users to a container that has started but whose application is not yet ready.",[239,1441,1443],{"id":1442},"which-aspnet-versions-have-built-in-health-checks","Which ASP.NET versions have built-in health checks?",[18,1445,1446,1447,1450],{},"If your application uses ASP.NET ",[46,1448,1449],{},"Core 2.2 or newer",", Microsoft provides a built-in Health Checks feature designed specifically for scenarios such as load balancers and container orchestrators.",[18,1452,1453,1454,214,1457,1460],{},"If you are running ",[46,1455,1456],{},"classic",[46,1458,1459],{},"Framework",", including .NET Framework 4.x applications, this built-in HTTP Health Checks feature is not available.",[18,1462,1463],{},"For classic ASP.NET Framework applications, you therefore need to create the health-check endpoint yourself.",[18,1465,1466,1467,214,1469,1471],{},"The example below is specifically for ",[46,1468,1456],{},[46,1470,1459],{}," applications.",[239,1473,1475],{"id":1474},"creating-a-simple-aspnet-framework-health-endpoint","Creating a simple ASP.NET Framework health endpoint",[18,1477,1478,1479,1482],{},"A very basic ",[548,1480,1481],{},"health.aspx"," could look like this:",[718,1484,1486],{"className":968,"code":1485,"language":970,"meta":116,"style":116},"\u003C%@ Page Language=\"C#\" EnableSessionState=\"false\" %>\n\u003C%@ Import Namespace=\"System\" %>\n\u003C%@ Import Namespace=\"System.Configuration\" %>\n\u003C%@ Import Namespace=\"System.Data.SqlClient\" %>\n\n\u003Cscript runat=\"server\">\n\nprotected void Page_Load(object sender, EventArgs e)\n{\n    Response.ContentType = \"text\u002Fplain\";\n    Response.TrySkipIisCustomErrors = true;\n\n    try\n    {\n        string connectionString =\n            ConfigurationManager\n                .ConnectionStrings[\"AppDatabase\"]\n                .ConnectionString;\n\n        using (SqlConnection connection =\n            new SqlConnection(connectionString))\n        {\n            connection.Open();\n\n            using (SqlCommand command =\n                new SqlCommand(\"SELECT 1\", connection))\n            {\n                command.CommandTimeout = 2;\n                command.ExecuteScalar();\n            }\n        }\n\n        Response.StatusCode = 200;\n        Response.Write(\"OK\");\n    }\n    catch\n    {\n        Response.StatusCode = 503;\n        Response.Write(\"UNHEALTHY\");\n    }\n}\n\n\u003C\u002Fscript>\n",[548,1487,1488,1493,1498,1503,1508,1512,1517,1521,1526,1531,1536,1541,1545,1550,1555,1560,1565,1571,1577,1582,1588,1594,1600,1606,1611,1617,1623,1629,1635,1641,1647,1653,1658,1664,1670,1676,1682,1687,1693,1699,1704,1710,1715],{"__ignoreMap":116},[726,1489,1490],{"class":728,"line":729},[726,1491,1492],{},"\u003C%@ Page Language=\"C#\" EnableSessionState=\"false\" %>\n",[726,1494,1495],{"class":728,"line":117},[726,1496,1497],{},"\u003C%@ Import Namespace=\"System\" %>\n",[726,1499,1500],{"class":728,"line":740},[726,1501,1502],{},"\u003C%@ Import Namespace=\"System.Configuration\" %>\n",[726,1504,1505],{"class":728,"line":746},[726,1506,1507],{},"\u003C%@ Import Namespace=\"System.Data.SqlClient\" %>\n",[726,1509,1510],{"class":728,"line":752},[726,1511,982],{"emptyLinePlaceholder":8},[726,1513,1514],{"class":728,"line":758},[726,1515,1516],{},"\u003Cscript runat=\"server\">\n",[726,1518,1519],{"class":728,"line":764},[726,1520,982],{"emptyLinePlaceholder":8},[726,1522,1523],{"class":728,"line":770},[726,1524,1525],{},"protected void Page_Load(object sender, EventArgs e)\n",[726,1527,1528],{"class":728,"line":776},[726,1529,1530],{},"{\n",[726,1532,1533],{"class":728,"line":1020},[726,1534,1535],{},"    Response.ContentType = \"text\u002Fplain\";\n",[726,1537,1538],{"class":728,"line":1026},[726,1539,1540],{},"    Response.TrySkipIisCustomErrors = true;\n",[726,1542,1543],{"class":728,"line":1032},[726,1544,982],{"emptyLinePlaceholder":8},[726,1546,1547],{"class":728,"line":1374},[726,1548,1549],{},"    try\n",[726,1551,1552],{"class":728,"line":1379},[726,1553,1554],{},"    {\n",[726,1556,1557],{"class":728,"line":1384},[726,1558,1559],{},"        string connectionString =\n",[726,1561,1562],{"class":728,"line":1389},[726,1563,1564],{},"            ConfigurationManager\n",[726,1566,1568],{"class":728,"line":1567},17,[726,1569,1570],{},"                .ConnectionStrings[\"AppDatabase\"]\n",[726,1572,1574],{"class":728,"line":1573},18,[726,1575,1576],{},"                .ConnectionString;\n",[726,1578,1580],{"class":728,"line":1579},19,[726,1581,982],{"emptyLinePlaceholder":8},[726,1583,1585],{"class":728,"line":1584},20,[726,1586,1587],{},"        using (SqlConnection connection =\n",[726,1589,1591],{"class":728,"line":1590},21,[726,1592,1593],{},"            new SqlConnection(connectionString))\n",[726,1595,1597],{"class":728,"line":1596},22,[726,1598,1599],{},"        {\n",[726,1601,1603],{"class":728,"line":1602},23,[726,1604,1605],{},"            connection.Open();\n",[726,1607,1609],{"class":728,"line":1608},24,[726,1610,982],{"emptyLinePlaceholder":8},[726,1612,1614],{"class":728,"line":1613},25,[726,1615,1616],{},"            using (SqlCommand command =\n",[726,1618,1620],{"class":728,"line":1619},26,[726,1621,1622],{},"                new SqlCommand(\"SELECT 1\", connection))\n",[726,1624,1626],{"class":728,"line":1625},27,[726,1627,1628],{},"            {\n",[726,1630,1632],{"class":728,"line":1631},28,[726,1633,1634],{},"                command.CommandTimeout = 2;\n",[726,1636,1638],{"class":728,"line":1637},29,[726,1639,1640],{},"                command.ExecuteScalar();\n",[726,1642,1644],{"class":728,"line":1643},30,[726,1645,1646],{},"            }\n",[726,1648,1650],{"class":728,"line":1649},31,[726,1651,1652],{},"        }\n",[726,1654,1656],{"class":728,"line":1655},32,[726,1657,982],{"emptyLinePlaceholder":8},[726,1659,1661],{"class":728,"line":1660},33,[726,1662,1663],{},"        Response.StatusCode = 200;\n",[726,1665,1667],{"class":728,"line":1666},34,[726,1668,1669],{},"        Response.Write(\"OK\");\n",[726,1671,1673],{"class":728,"line":1672},35,[726,1674,1675],{},"    }\n",[726,1677,1679],{"class":728,"line":1678},36,[726,1680,1681],{},"    catch\n",[726,1683,1685],{"class":728,"line":1684},37,[726,1686,1554],{},[726,1688,1690],{"class":728,"line":1689},38,[726,1691,1692],{},"        Response.StatusCode = 503;\n",[726,1694,1696],{"class":728,"line":1695},39,[726,1697,1698],{},"        Response.Write(\"UNHEALTHY\");\n",[726,1700,1702],{"class":728,"line":1701},40,[726,1703,1675],{},[726,1705,1707],{"class":728,"line":1706},41,[726,1708,1709],{},"}\n",[726,1711,1713],{"class":728,"line":1712},42,[726,1714,982],{"emptyLinePlaceholder":8},[726,1716,1718],{"class":728,"line":1717},43,[726,1719,1720],{},"\u003C\u002Fscript>\n",[18,1722,1723],{},"In this example, the endpoint does two useful things.",[18,1725,1726,1727,1730],{},"First, simply reaching ",[548,1728,1729],{},"Page_Load"," proves that IIS and ASP.NET Framework are capable of processing the request.",[18,1732,1733],{},"Second, it opens a connection to SQL Server and executes:",[718,1735,1739],{"className":1736,"code":1737,"language":1738,"meta":116,"style":116},"language-sql shiki shiki-themes github-light github-dark","SELECT 1\n","sql",[548,1740,1741],{"__ignoreMap":116},[726,1742,1743],{"class":728,"line":729},[726,1744,1737],{},[18,1746,1747],{},"This is deliberately a very small query. We do not care about application data here; we only want to know whether the application can establish a connection to the database and successfully execute a query.",[18,1749,1750],{},"If everything works, the endpoint returns:",[718,1752,1755],{"className":1753,"code":1754,"language":819,"meta":116},[817],"200 OK\n",[548,1756,1754],{"__ignoreMap":116},[18,1758,1759],{},"If the database connection or query fails, it returns:",[718,1761,1764],{"className":1762,"code":1763,"language":819,"meta":116},[817],"503 Service Unavailable\n",[548,1765,1763],{"__ignoreMap":116},[18,1767,546,1768,1771],{},[548,1769,1770],{},"EnableSessionState=\"false\""," setting is also intentional. The health-check request itself does not need to create an ASP.NET user session.",[18,1773,1774],{},"If your application depends on SQL Server session state, you can explicitly test that SQL Server as another dependency rather than allowing the health-check request itself to create a session.",[234,1776,1778],{"id":1777},"final-thoughts","Final thoughts",[18,1780,1781],{},"Running ASP.NET on AWS is absolutely possible, and Fargate gives you a relatively simple way to do it without having to manage Windows servers yourself.",[18,1783,1784],{},"The biggest differences compared with running Linux containers are the larger Windows images, higher cost, slower startup times, and the reduced set of Fargate features available to Windows workloads.",[18,1786,1787],{},"The other important part is making sure your application can run across multiple containers. Older ASP.NET applications commonly store session data in the memory of the IIS process, which works fine on one server but becomes a problem once you start autoscaling.",[18,1789,1790],{},"Fortunately, ASP.NET already provides ways to move session state outside the application, such as storing it in SQL Server. In many cases, this means you can make an existing application work properly with Fargate without having to rewrite the application from scratch.",[18,1792,1793],{},"Once the application is containerized and no longer depends on the state of a particular server, the rest becomes standard AWS infrastructure: push the image to ECR, run it with ECS and Fargate, place the tasks behind an Application Load Balancer, configure the target group health check, and scale the service based on demand.",[18,1795,1796],{},"Azure may be the obvious place to look when you have an ASP.NET application, but it is certainly not the only option. If the rest of your infrastructure is already on AWS, there is very little reason to move everything somewhere else just because one application happens to be written in ASP.NET.",[234,1798,1800],{"id":1799},"frequently-asked-questions","Frequently Asked Questions",[239,1802,1804],{"id":1803},"can-you-host-a-legacy-aspnet-framework-application-on-aws","Can you host a legacy ASP.NET Framework application on AWS?",[18,1806,1807],{},"Yes. A legacy ASP.NET Framework application can run on AWS without being rewritten from scratch. One option is to package the application as a Windows container, store the image in Amazon ECR, and run it with Amazon ECS on AWS Fargate.",[239,1809,1811],{"id":1810},"can-aws-fargate-run-aspnet-framework-applications","Can AWS Fargate run ASP.NET Framework applications?",[18,1813,1814],{},"Yes. AWS Fargate can run ASP.NET Framework applications inside Windows containers. Fargate runs the Windows container while ECS handles orchestration, allowing you to deploy and scale the application without maintaining the underlying Windows servers yourself.",[239,1816,1818],{"id":1817},"can-you-run-iis-on-aws-fargate","Can you run IIS on AWS Fargate?",[18,1820,1821],{},"Yes. IIS can run inside a Windows container on AWS Fargate. Microsoft provides ASP.NET Framework container images that already include Windows Server Core, IIS, and ASP.NET, so you do not need to install and configure IIS manually inside your container.",[239,1823,1825],{"id":1824},"does-aws-fargate-support-net-framework-48","Does AWS Fargate support .NET Framework 4.8?",[18,1827,1828],{},"Yes, provided the application can run inside a compatible Windows container. Microsoft provides an official ASP.NET Framework 4.8 container image containing Windows Server Core, IIS, and ASP.NET. AWS Fargate currently supports Windows Server 2019 and Windows Server 2022 containers.",[239,1830,1832],{"id":1831},"do-i-need-to-rewrite-my-aspnet-application-before-moving-it-to-aws","Do I need to rewrite my ASP.NET application before moving it to AWS?",[18,1834,1835,1836,1838],{},"Not necessarily. Many existing ASP.NET Framework applications can be containerized with relatively few changes. The main issue is usually application state. If the application stores user sessions in IIS memory using ",[548,1837,633],{},", the session state should be moved to a shared store before running multiple Fargate tasks.",[239,1840,1842],{"id":1841},"how-should-aspnet-session-state-work-with-aws-fargate","How should ASP.NET session state work with AWS Fargate?",[18,1844,1845,1846,1848,1849,1851],{},"ASP.NET session state should normally be stored outside the container when running multiple Fargate tasks. For example, ASP.NET Framework can use ",[548,1847,682],{}," session mode instead of the default ",[548,1850,633],{}," mode. This allows different containers to access the same session data when ECS scales or replaces tasks.",[239,1853,1855],{"id":1854},"how-much-does-it-cost-to-run-aspnet-on-aws-fargate","How much does it cost to run ASP.NET on AWS Fargate?",[18,1857,1858,1859,1862],{},"Windows Fargate is considerably more expensive than an equivalent Linux task. Using the US East (N. Virginia) pricing in this guide, a continuously running Windows task with 1 vCPU and 2 GB of memory costs about ",[46,1860,1861],{},"$113.40 per 30 days",", before load balancers, networking, logging, and other AWS services.",[239,1864,1866],{"id":1865},"what-are-the-main-limitations-of-running-aspnet-on-windows-fargate","What are the main limitations of running ASP.NET on Windows Fargate?",[18,1868,1869],{},"The main limitations are larger Windows container images, higher compute costs, slower container startup times, and fewer supported Fargate features. Windows Fargate also does not currently support features including Fargate Spot, Amazon EFS, Amazon EBS, Amazon FSx, or ARM64 workloads.",[239,1871,1873],{"id":1872},"is-aws-fargate-a-good-choice-for-legacy-aspnet-applications","Is AWS Fargate a good choice for legacy ASP.NET applications?",[18,1875,1876],{},"It can be. Fargate is particularly useful when you want to keep an existing ASP.NET Framework application on AWS without managing Windows servers. The trade-offs are the higher cost of Windows containers, larger images, slower startup times, and the need to make application state work correctly across multiple containers.",[1878,1879,1880],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":116,"searchDepth":117,"depth":117,"links":1882},[1883,1894,1903,1910,1913,1917,1918],{"id":236,"depth":117,"text":237,"children":1884},[1885,1886,1887,1888,1889,1890,1891,1892,1893],{"id":241,"depth":740,"text":242},{"id":257,"depth":740,"text":258},{"id":271,"depth":740,"text":272},{"id":397,"depth":740,"text":398},{"id":482,"depth":740,"text":483},{"id":489,"depth":740,"text":490},{"id":564,"depth":740,"text":565},{"id":584,"depth":740,"text":585},{"id":598,"depth":740,"text":599},{"id":619,"depth":117,"text":620,"children":1895},[1896,1897,1898,1899,1900,1901,1902],{"id":623,"depth":740,"text":624},{"id":644,"depth":740,"text":645},{"id":660,"depth":740,"text":661},{"id":703,"depth":740,"text":704},{"id":788,"depth":740,"text":789},{"id":850,"depth":740,"text":851},{"id":896,"depth":740,"text":897},{"id":909,"depth":117,"text":910,"children":1904},[1905,1906,1907,1908,1909],{"id":913,"depth":740,"text":914},{"id":943,"depth":740,"text":944},{"id":1041,"depth":740,"text":1042},{"id":1096,"depth":740,"text":1097},{"id":1142,"depth":740,"text":1143},{"id":1193,"depth":117,"text":1194,"children":1911},[1912],{"id":1249,"depth":740,"text":1250},{"id":1404,"depth":117,"text":1405,"children":1914},[1915,1916],{"id":1442,"depth":740,"text":1443},{"id":1474,"depth":740,"text":1475},{"id":1777,"depth":117,"text":1778},{"id":1799,"depth":117,"text":1800,"children":1919},[1920,1921,1922,1923,1924,1925,1926,1927,1928],{"id":1803,"depth":740,"text":1804},{"id":1810,"depth":740,"text":1811},{"id":1817,"depth":740,"text":1818},{"id":1824,"depth":740,"text":1825},{"id":1831,"depth":740,"text":1832},{"id":1841,"depth":740,"text":1842},{"id":1854,"depth":740,"text":1855},{"id":1865,"depth":740,"text":1866},{"id":1872,"depth":740,"text":1873},"\u002Fpublic\u002Fimg\u002Farticles\u002Fhow-to-host-a-legacy-aspnet-framework-application-on-aws-fargate\u002Fcover.jpg","2026-08-14","Learn how to run legacy ASP.NET Framework apps on AWS Fargate using Windows containers, ECS, SQL Server session state, health checks, and more.","\u002Fpublic\u002Fimg\u002Farticles\u002Fhow-to-host-a-legacy-aspnet-framework-application-on-aws-fargate\u002Ffeatured.jpg",{},"\u002Farticles\u002Fhow_to\u002F2026-08-14-how-to-host-a-legacy-aspnet-framework-application-on-aws-fargate","\u002Fpublic\u002Fimg\u002Farticles\u002Fhow-to-host-a-legacy-aspnet-framework-application-on-aws-fargate\u002Fpreview.jpg",{"title":205,"description":1931},"articles\u002Fhow_to\u002F2026-08-14-how-to-host-a-legacy-aspnet-framework-application-on-aws-fargate","SBJX_WUBt6duC-IZxBU0BnqIe4essc0kuNyNQXcPHNQ",{"id":11,"title":12,"author":13,"body":1940,"category":119,"cover":120,"date":121,"description":122,"extension":123,"featured":124,"isFeatured":125,"meta":2002,"navigation":8,"path":127,"preview":128,"seo":2003,"stem":130,"updated":131,"__hash__":132},{"type":15,"value":1941,"toc":2000},[1942,1944,1946,1948,1950,1952,1954,1968,1970,1972,1985,1987,1989,1991,1993,1995],[18,1943,20],{},[22,1945,25],{"id":24},[18,1947,28],{},[18,1949,31],{},[22,1951,35],{"id":34},[18,1953,38],{},[40,1955,1956,1960,1964],{},[43,1957,1958,49],{},[46,1959,48],{},[43,1961,1962,55],{},[46,1963,54],{},[43,1965,1966,61],{},[46,1967,60],{},[22,1969,65],{"id":64},[18,1971,68],{},[40,1973,1974,1979,1981,1983],{},[43,1975,73,1976,81],{},[75,1977,80],{"href":77,"rel":1978},[79],[43,1980,84],{},[43,1982,87],{},[43,1984,90],{},[18,1986,93],{},[18,1988,96],{},[22,1990,100],{"id":99},[18,1992,103],{},[22,1994,107],{"id":106},[18,1996,110,1997,81],{},[75,1998,114],{"href":77,"rel":1999},[79],{"title":116,"searchDepth":117,"depth":117,"links":2001},[],{},{"title":12,"description":122},1789238836123]